Coverage for /opt/cloud/slavespace/usr1/096471637100f3de0fcfc01072822a80/ut/src/asys/cmdline/cmd_parser.py: 99%
160 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-08-21 15:37 +0800
« prev ^ index » next coverage.py v7.14.1, created at 2026-08-21 15:37 +0800
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# ----------------------------------------------------------------------------
4# Copyright (c) 2025 Huawei Technologies Co., Ltd.
5#
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17# ----------------------------------------------------------------------------
19import argparse
20import sys
21import textwrap
22import enum
24from params import ParamDict
25from common.const import RetCode, consts
26from cmdline.arg_checker import ArgChecker
28KEY_NAME = "name"
29KEY_TYPE = "type"
30KEY_HELP = "help"
31KEY_CHECKER = "checker"
32KEY_REQUIRED = "required"
33KEY_ARGS = "args"
34KEY_CHOICES = "choices"
35KEY_METAVAR = "metavar"
36KEY_ACTION = "action"
38OPTIONAL_Y = "\033[33m<Optional>\033[0m" # yellow
39POSITIONAL_R = "\033[31m<Positional>\033[0m" # red
42class Arg(enum.Enum):
43 """The support arg."""
44 TASK_DIR = {
45 KEY_NAME: "task_dir", KEY_CHECKER: ArgChecker.DIR_EXIST, KEY_REQUIRED: False, KEY_METAVAR: " ",
46 KEY_HELP: f"{OPTIONAL_Y} Specifies the directory for collecting operator build files, GE dump graphs, "
47 "and TF Adapter dump graphs. If task_dir is not set, these files are not collected by default.",
48 }
49 TASK = {
50 KEY_NAME: "task", KEY_CHECKER: ArgChecker.EXECUTABLE, KEY_REQUIRED: True, KEY_METAVAR: " ",
51 KEY_HELP: f"{POSITIONAL_R} Specifies the execution command for the service. "
52 "It collects maintenance and debugging information during command execution."
53 }
54 OUTPUT = {
55 KEY_NAME: "output", KEY_CHECKER: ArgChecker.DIR_CREATE, KEY_REQUIRED: False, KEY_METAVAR: " ",
56 KEY_HELP: f"{OPTIONAL_Y} Specifies the path to save the command execution results, Default: current dir."
57 }
58 TAR = {
59 KEY_NAME: "tar", KEY_CHECKER: ArgChecker.TAR_CHECK, KEY_REQUIRED: False, KEY_METAVAR: " ",
60 KEY_HELP: f"{OPTIONAL_Y} Specifies whether to compress the asys result directory into a tar.gz file."
61 " The original directory is not retained after compression. No compression by default."
62 }
63 COLLECT_RUN = {
64 KEY_NAME: "r", KEY_CHECKER: None, KEY_REQUIRED: False, KEY_CHOICES: ['stacktrace'],
65 KEY_HELP: f"{OPTIONAL_Y} Specifies the collect logs mode, this parameter must be used together with '--remote'"
66 " and '--all'. It can be set to 'stacktrace' (send signal to the process specified by remote, "
67 "and generating the stackcore file). "
68 "If r is not set, collects existing maintenance and debugging information in the environment."
69 }
70 REMOTE = {
71 KEY_NAME: "remote", KEY_TYPE: int, KEY_CHECKER: None, KEY_REQUIRED: False, KEY_METAVAR: " ",
72 KEY_HELP: f"{OPTIONAL_Y} Specifies the ID of the process that receives signal, "
73 "this parameter must be used together with '-r=stacktrace'."
74 }
75 ALL = {
76 KEY_NAME: "all", KEY_CHECKER: None, KEY_REQUIRED: False, KEY_METAVAR: " ",
77 KEY_HELP: f"{OPTIONAL_Y} Specifies the stackcore files for all tasks, this parameter must be used together with"
78 " '-r=stacktrace'."
79 }
80 QUIET = {
81 KEY_NAME: "quiet", KEY_CHECKER: None, KEY_REQUIRED: False, KEY_METAVAR: " ",
82 KEY_HELP: f"{OPTIONAL_Y} Disable the interaction function during stack information export, "
83 "this parameter must be used together with '-r=stacktrace'."
84 }
85 STACKTRACE_TIMEOUT = {
86 KEY_NAME: "timeout", KEY_TYPE: int, KEY_CHECKER: None, KEY_REQUIRED: False, KEY_METAVAR: " ",
87 KEY_HELP: f"{OPTIONAL_Y} Specifies the stacktrace collect duration, in seconds, value range: [1, 60]. "
88 "If this argument is not specified, the default 10s is used."
89 }
91 DEVICE = {
92 KEY_NAME: "d", KEY_TYPE: int, KEY_CHECKER: ArgChecker.DEVICE_ID, KEY_REQUIRED: False, KEY_METAVAR: " ",
93 KEY_HELP: f"{OPTIONAL_Y} Specifies the ID of the device for command execution."
94 }
96 ANALYZE_DEVICE = {
97 KEY_NAME: "d", KEY_TYPE: int, KEY_CHECKER: ArgChecker.DEVICE_ID, KEY_REQUIRED: False, KEY_METAVAR: " ",
98 KEY_HELP: f"{OPTIONAL_Y} Specifies the ID of the device for command execution. This argument "
99 "is valid only for 'aicore_error'."
100 }
102 DIS_RUN = {
103 KEY_NAME: "r", KEY_CHECKER: None, KEY_REQUIRED: True,
104 KEY_CHOICES: ['stress_detect', 'hbm_detect', 'cpu_detect', 'component'],
105 KEY_HELP: f"{POSITIONAL_R} Specifies the hardware detection mode. It can be set to 'stress_detect' (AI Core "
106 "stress test), 'hbm_detect' (HBM detection), 'cpu_detect' (CPU detection) or "
107 "'component' (Operator detection)."
108 }
109 TIMEOUT = {
110 KEY_NAME: "timeout", KEY_TYPE: int, KEY_CHECKER: None, KEY_REQUIRED: False, KEY_METAVAR: " ",
111 KEY_HELP: f"{OPTIONAL_Y} Specifies the detection duration, in seconds. "
112 "In HBM detection mode, value range: [0, 604800]. In CPU detection mode, value range: [1, 604800]. "
113 "If this argument is not specified, the default 600s is used."
114 }
116 INFO_RUN = {
117 KEY_NAME: "r", KEY_CHECKER: None, KEY_REQUIRED: True,
118 KEY_CHOICES: ['hardware', 'software', 'status'],
119 KEY_HELP: f"{POSITIONAL_R} Specifies the type of information to be collected."
120 " It can be set to 'status' (device information), 'software' (software information of the host), "
121 "or 'hardware' (hardware information of the host and device)."
122 }
124 ANALYZE_RUN = {
125 KEY_NAME: "r", KEY_CHECKER: None, KEY_REQUIRED: True,
126 KEY_CHOICES: ["trace", "coredump", "coretrace", "stackcore", "aicore_error", "ub"],
127 KEY_HELP: f"{POSITIONAL_R} Specifies the type of data to be analyzed. It can be set to 'trace' "
128 "(trace binary file), 'coredump' (system core file), 'coretrace' (coretrace file), "
129 "'stackcore' (stackcore file), 'aicore_error' (aicore error dump and log) or "
130 "'ub' (UB binary files)."
131 }
132 FILE = {
133 KEY_NAME: "file", KEY_CHECKER: ArgChecker.FILE_PATH_EXIST_R, KEY_REQUIRED: False, KEY_METAVAR: " ",
134 KEY_HELP: f"{POSITIONAL_R} Specifies the single file to be analyzed. This argument is valid only for 'trace', "
135 "'coretrace' and 'stackcore'. Mutually exclusive with '--path'."
136 }
137 PATH = {
138 KEY_NAME: "path", KEY_CHECKER: ArgChecker.FILE_PATH_EXIST_R, KEY_REQUIRED: False, KEY_METAVAR: " ",
139 KEY_HELP: f"{POSITIONAL_R} Specifies the path to be analyzed. This argument is valid only for 'trace', "
140 "'coretrace' and 'stackcore'. Mutually exclusive with '--file'."
141 }
142 EXE_FILE = {
143 KEY_NAME: "exe_file", KEY_CHECKER: None, KEY_REQUIRED: False, KEY_METAVAR: " ",
144 KEY_HELP: f"{POSITIONAL_R} Specifies the executable file to be debugged. "
145 "This argument is valid only for 'coredump'."
146 }
147 CORE_FILE = {
148 KEY_NAME: "core_file", KEY_CHECKER: ArgChecker.CORE_FILE, KEY_REQUIRED: False, KEY_METAVAR: " ",
149 KEY_HELP: f"{POSITIONAL_R} Specifies the core file to be debugged. This argument is valid only for 'coredump'."
150 }
151 SYMBOL = {
152 KEY_NAME: "symbol", KEY_TYPE: int, KEY_CHECKER: None, KEY_REQUIRED: False, KEY_CHOICES: [0, 1],
153 KEY_HELP: f"{OPTIONAL_Y} Specifies whether to retain the stack frame information that fails to be analyzed "
154 "in the result (represented by double questions marks '??'). "
155 "This argument is valid only for 'coredump'. Defaults to 0, indicating not to retain."
156 }
157 SYMBOL_PATH = {
158 KEY_NAME: "symbol_path", KEY_CHECKER: ArgChecker.SYMBOL_PATH, KEY_REQUIRED: False, KEY_METAVAR: " ",
159 KEY_HELP: f"{OPTIONAL_Y} Specifies the path of executable files and dependent dynamic library files. "
160 "Subpaths are not searched. This argument is valid only for 'stackcore'. "
161 "Defaults to the dynamic library path in the stackcore file."
162 }
163 REG = {
164 KEY_NAME: "reg", KEY_TYPE: int, KEY_CHECKER: None, KEY_REQUIRED: False, KEY_CHOICES: [0, 1, 2],
165 KEY_HELP: f"{OPTIONAL_Y} Specifies the mode of adding register data for analysis. "
166 "0: not add; 1: add only for threads; 2: add for all stack frames. Defaults to 0."
167 }
168 GET = {
169 KEY_NAME: "get", KEY_CHECKER: None, KEY_REQUIRED: False, KEY_METAVAR: " ",
170 KEY_HELP: f"{OPTIONAL_Y} Gets the configuration. Use either this argument or '--restore'."
171 }
172 RESTORE = {
173 KEY_NAME: "restore", KEY_CHECKER: None, KEY_REQUIRED: False, KEY_METAVAR: " ",
174 KEY_HELP: f"{OPTIONAL_Y} Restores the configuration. Use either this argument or '--get'."
175 }
176 STRESS_DETECT = {
177 KEY_NAME: "stress_detect", KEY_CHECKER: None, KEY_REQUIRED: True, KEY_METAVAR: " ",
178 KEY_HELP: f"{POSITIONAL_R} Specifies the configuration options to be queried or restored, "
179 "indicating the configurations related to the pressure test."
180 }
181 PROFILING_RUN = {
182 KEY_NAME: "r", KEY_CHECKER: None, KEY_REQUIRED: True, KEY_METAVAR: " ",
183 KEY_HELP: f"{POSITIONAL_R} Specifies the type of profile information to be collected. "
184 "It can be set to 'aicore' (aicore information), 'dvpp' (dvpp information), "
185 "'memory' (hardware memory information), 'link' (interconnection information), "
186 "'os' (system information), 'power' (low power information), "
187 "or any combination of these values, separated by ','."
188 }
189 PERIOD = {
190 KEY_NAME: "p", KEY_TYPE: int, KEY_CHECKER: None, KEY_REQUIRED: True, KEY_METAVAR: " ",
191 KEY_HELP: f"{POSITIONAL_R} Specifies the profile information collection period, in seconds. "
192 "Value range: [1, 2592000]."
193 }
194 AIC_METRICS = {
195 KEY_NAME: "aic_metrics", KEY_CHECKER: None, KEY_REQUIRED: False, KEY_METAVAR: " ",
196 KEY_CHOICES: ["PipeUtilization", "ArithmeticUtilization", "Memory", "MemoryL0",
197 "MemoryUB", "ResourceConflictRatio", "L2Cache", "MemoryAccess"],
198 KEY_HELP: f"{OPTIONAL_Y} Specifies the aicore metrics to be collected. "
199 "It takes effect when the run mode includes aicore. It can be set to 'PipeUtilization' "
200 "(time consumption and proportion of computing unit and handling unit), "
201 "'ArithmeticUtilization' (time consumption and proportion of cube and vector instructions), "
202 "'Memory' (Memory IO bandwidth), 'MemoryL0' (L0 IO bandwidth), 'MemoryUB' (UB IO bandwidth), "
203 "'ResourceConflictRatio' (percentage of pipeline queue class instructions) or "
204 "'L2Cache' (read/write cache hit count and re-allocation count after misses). "
205 "'MemoryAccess' (Bandwidth and data volume of operators accessing memory on the aicore). "
206 "If this argument is not specified, the default PipeUtilization is used."
207 }
210class Command(enum.Enum):
211 """The support command."""
212 COLLECT = {
213 KEY_NAME: "collect",
214 KEY_ARGS: [Arg.TASK_DIR, Arg.OUTPUT, Arg.TAR, Arg.COLLECT_RUN, Arg.REMOTE, Arg.ALL, Arg.QUIET,
215 Arg.STACKTRACE_TIMEOUT],
216 KEY_HELP: "Collects existing maintenance and debugging information in the environment, "
217 "or export stacktrace information in real time."
218 }
219 LAUNCH = {
220 KEY_NAME: "launch",
221 KEY_ARGS: [Arg.TASK, Arg.OUTPUT, Arg.TAR],
222 KEY_HELP: "Executes the script of task parameters, and collects the maintenance "
223 "and debugging information during the script execution."
224 }
225 DIAGNOSE = {
226 KEY_NAME: "diagnose",
227 KEY_ARGS: [Arg.DIS_RUN, Arg.DEVICE, Arg.TIMEOUT, Arg.OUTPUT],
228 KEY_HELP: "Diagnoses the hardware status of the device. It has diagnostic capabilities for "
229 "component, stress_detect, hbm_detect and cpu_detect. "
230 "The detect diagnostic only supports [910B, 910_93, 950]. "
231 }
232 HEALTH = {
233 KEY_NAME: "health",
234 KEY_ARGS: [Arg.DEVICE],
235 KEY_HELP: "Diagnoses the health status of the device."
236 }
237 INFO = {
238 KEY_NAME: "info",
239 KEY_ARGS: [Arg.INFO_RUN, Arg.DEVICE],
240 KEY_HELP: "Collects the software and hardware information of the host and device."
241 }
242 ANALYZE = {
243 KEY_NAME: "analyze",
244 KEY_ARGS: [Arg.ANALYZE_RUN, Arg.ANALYZE_DEVICE, Arg.FILE, Arg.PATH, Arg.EXE_FILE, Arg.CORE_FILE, Arg.SYMBOL,
245 Arg.SYMBOL_PATH, Arg.REG, Arg.OUTPUT],
246 KEY_HELP: "Analyzes the trace, coredump, coretrace, stackcore, aicore_error and ub info."
247 }
248 CONFIG = {
249 KEY_NAME: "config",
250 KEY_ARGS: [Arg.GET, Arg.DEVICE, Arg.RESTORE, Arg.STRESS_DETECT],
251 KEY_HELP: "Gets or restores configuration information."
252 }
253 PROFILING = {
254 KEY_NAME: "profiling",
255 KEY_ARGS: [Arg.DEVICE, Arg.PROFILING_RUN, Arg.OUTPUT, Arg.PERIOD, Arg.AIC_METRICS],
256 KEY_HELP: "Collects the profiling information of the device."
257 }
260class CommandLineParser:
261 """
262 The definition of command line parser.
263 """
265 def __init__(self):
266 description_msg = textwrap.dedent('''\
267 command help:
268 asys {command} [-h, --help]
269 ''')
270 self.parser = argparse.ArgumentParser(prog="asys", formatter_class=argparse.RawDescriptionHelpFormatter,
271 description=description_msg)
272 subparsers = self.parser.add_subparsers(dest='subparser_name', help='asys supported commands')
273 self.__config_parser = None
275 # Config the parser from Command and Args
276 for cmd in Command:
277 cmd_conf = cmd.value
278 # analyze, diagnose only support EP
279 if ParamDict().get_env_type() == "RC" and cmd_conf[KEY_NAME] not in [consts.collect_cmd, consts.launch_cmd]:
280 continue
281 parser = subparsers.add_parser(cmd_conf[KEY_NAME], help=cmd_conf[KEY_HELP], allow_abbrev=False)
282 if cmd_conf[KEY_NAME] == consts.config_cmd:
283 self.__config_parser = parser
284 self.__set_config_cmd_parser(parser, cmd_conf)
285 continue
286 if cmd_conf[KEY_NAME] == consts.analyze_cmd:
287 self.__set_analyze_cmd_parser(parser, cmd_conf)
288 continue
290 supported_args = cmd_conf[KEY_ARGS]
291 for arg in supported_args:
292 arg_conf = arg.value
293 if arg_conf.get(KEY_NAME) in ['d', 'r', 'p']:
294 arg_name = "-" + arg_conf[KEY_NAME]
295 else:
296 arg_name = "--" + arg_conf[KEY_NAME]
297 _metavar = " "
298 if arg_conf.get(KEY_CHOICES):
299 _metavar = None
300 if arg_conf[KEY_NAME] in ["all", "quiet"]:
301 parser.add_argument(
302 arg_name, required=arg_conf[KEY_REQUIRED], action="store_true", help=arg_conf[KEY_HELP]
303 )
304 else:
305 parser.add_argument(
306 arg_name, type=arg_conf.get(KEY_TYPE, str), required=arg_conf[KEY_REQUIRED],
307 choices=arg_conf.get(KEY_CHOICES), help=arg_conf[KEY_HELP], metavar=arg_conf.get(KEY_METAVAR)
308 )
310 @staticmethod
311 def __set_config_cmd_parser(parser, cmd_conf):
312 # 不使用 add_mutually_exclusive_group:Python 3.14 的 argparse 会将互斥
313 # 组成员合并渲染为 "[--get | --restore]",与历史 3.12 风格 "[--get] [-d ]
314 # [--restore]"(保留参数声明顺序)不一致。这里改为常规 add_argument,
315 # 在 parse() 阶段手动校验互斥关系,并复用 argparse 错误信息格式。
316 supported_args = cmd_conf[KEY_ARGS]
317 for arg in supported_args:
318 arg_conf = arg.value
319 arg_name = "--" + arg_conf[KEY_NAME]
320 if arg_conf.get(KEY_NAME) == 'd':
321 arg_name = "-" + arg_conf[KEY_NAME]
322 parser.add_argument(
323 arg_name, type=arg_conf.get(KEY_TYPE, str), required=arg_conf[KEY_REQUIRED],
324 choices=arg_conf.get(KEY_CHOICES), help=arg_conf[KEY_HELP], metavar=arg_conf.get(KEY_METAVAR)
325 )
326 continue
328 parser.add_argument(arg_name, required=arg_conf[KEY_REQUIRED], action="store_true",
329 help=arg_conf[KEY_HELP])
331 @staticmethod
332 def __set_analyze_cmd_parser(parser, cmd_conf):
333 group = parser.add_mutually_exclusive_group(required=False)
334 supported_args = cmd_conf[KEY_ARGS]
335 for arg in supported_args:
336 arg_conf = arg.value
337 arg_name = "--" + arg_conf[KEY_NAME]
338 if arg_conf.get(KEY_NAME) in ['d', 'r']:
339 arg_name = "-" + arg_conf[KEY_NAME]
341 if arg_conf.get(KEY_NAME) in ["file", 'path']:
342 group.add_argument(arg_name, type=arg_conf.get(KEY_TYPE, str), required=arg_conf[KEY_REQUIRED],
343 help=arg_conf[KEY_HELP], metavar=arg_conf.get(KEY_METAVAR))
344 else:
345 parser.add_argument(
346 arg_name, type=arg_conf.get(KEY_TYPE, str), required=arg_conf[KEY_REQUIRED],
347 choices=arg_conf.get(KEY_CHOICES), help=arg_conf[KEY_HELP], metavar=arg_conf.get(KEY_METAVAR)
348 )
350 def print_help(self):
351 """Print the help information generated by parser"""
352 self.parser.print_help()
354 @classmethod
355 def check_arg_with_checker(cls, arg_name, arg_val, checker):
356 """
357 Check arg with checker.
359 Args:
360 arg_val: The value of arg to check
361 checker: The check function to use
363 Returns:
364 RetCode: return code (SUCCESS:0, FAILED:1)
365 """
366 if checker is None:
367 return RetCode.SUCCESS
368 return checker(arg_name, arg_val)
370 @classmethod
371 def match_command(cls, cmd):
372 """
373 Match input command to Enum cmd type.
375 Args:
376 cmd: Input command
378 Returns:
379 command: Enum cmd type
380 """
381 for command in Command:
382 command_conf = command.value
383 if command_conf[KEY_NAME] == cmd:
384 return command
385 return None
387 @classmethod
388 def check_args(cls, args):
389 """
390 Check args according to command type.
392 Args:
393 args: The namesapce returned by parse_args
395 Returns:
396 RetCode: return code (SUCCESS:0, FAILED:1)
397 """
398 input_cmd = args.subparser_name
399 command = CommandLineParser.match_command(input_cmd)
400 if not command:
401 return RetCode.FAILED
402 supported_args = command.value[KEY_ARGS]
403 for support_arg in supported_args:
404 arg_info = support_arg.value
405 arg_val = getattr(args, arg_info[KEY_NAME])
406 if arg_val is None:
407 continue # this arg is optional and not set, check next arg
408 checker = arg_info[KEY_CHECKER]
409 ret = CommandLineParser.check_arg_with_checker(arg_info[KEY_NAME], arg_val, checker)
410 if ret != RetCode.SUCCESS:
411 return RetCode.FAILED
412 return RetCode.SUCCESS
414 def parse(self):
415 """
416 Parse the command and args from cmd line.
418 Returns:
419 RetCode: return code (SUCCESS:0, FAILED:1)
420 """
421 # config 子命令的 --get / --restore 互斥校验:原本通过 argparse 的
422 # mutually_exclusive_group 实现,但 3.14 改变了 usage 渲染顺序,故改为
423 # 手动校验。在 parse_args 之前预扫描 argv,确保互斥错误优先于 required
424 # 缺失错误抛出,与历史 mutex group 行为一致;错误信息复用
425 # subparser.error(),与原 argparse 输出格式完全相同。
426 self.__check_config_mutex_in_argv()
427 args = self.parser.parse_args()
428 if args.subparser_name is None: # -h, --help, and only asys
429 return RetCode.SUCCESS
430 if CommandLineParser.check_args(args) == RetCode.FAILED:
431 return RetCode.FAILED
432 ParamDict().set_args(args)
433 return RetCode.SUCCESS
435 def __check_config_mutex_in_argv(self):
436 if self.__config_parser is None:
437 return
438 argv = sys.argv[1:]
439 if not argv or argv[0] != consts.config_cmd:
440 return
441 sub_argv = argv[1:]
442 has_get = any(tok == "--get" or tok.startswith("--get=") for tok in sub_argv)
443 has_restore = any(tok == "--restore" or tok.startswith("--restore=") for tok in sub_argv)
444 if has_get and has_restore:
445 self.__config_parser.error("argument --restore: not allowed with argument --get")