已合并
【msserviceprofiler】服务化调优支持--help和--version选项 #441
hewenbo创建于 8月15日
【msserviceprofiler】服务化调优支持--help和--version选项 #441
已合并
hewenbo创建于 8月15日
已删除 :master合入到Ascend/msserviceprofilermaster
4 个文件变更+276-33
@@ -14,34 +14,27 @@
14# See the Mulan PSL v2 for more details.14# See the Mulan PSL v2 for more details.
15# -------------------------------------------------------------------------15# -------------------------------------------------------------------------
16 16 
17-import argparse17+from ms_service_profiler.cli import create_subcommand_parser, run_parser
18-from msservice_advisor.msservice_advisor import advisor
19-from ms_serviceparam_optimizer.ms_serviceparam_optimizer.optimizer import optimizer
20-from ms_serviceparam_optimizer.ms_serviceparam_optimizer.train import source_to_train
21-from ms_service_profiler import compare, split, analyze
22 18 
23 19 
24def main():20def main():
25- 21+ parser, subparsers = create_subcommand_parser()
26- parser = argparse.ArgumentParser(22+ if subparsers is None:
27- formatter_class=argparse.ArgumentDefaultsHelpFormatter,23+ return
28- description="[MindStudio] msserviceprofiler command line tool"24+ 
29- )25+ from msservice_advisor.msservice_advisor import advisor
atomgit-bot
atomgit-botatomgit-bot8月15日

🟡 Medium Priority

变更后的根入口 __main__.py 使用共享的 create_parser(),其返回的 RootHelpParserprog == "msserviceprofiler" 时,format_help() 直接返回静态的 ROOT_HELP(cli.py 第 30-69 行,只列出了 analyze/parse/split/compare 四个 profiler 子命令)。

而该根入口在 is_root_cli_request() 为 False 时会注册 7 个子命令(第 34-40 行):source_to_trainoptimizeradvisoranalyzeparsesplitcompare

由此形成不一致:当用户执行 msserviceprofiler --helpmsserviceprofiler(不带参数)时,is_root_cli_request() 返回 True,直接走 run_parser(parser)print_help → 返回静态 ROOT_HELP,只显示 4 个 profiler 子命令,optimizeradvisorsource_to_train 三个真实存在的子命令在帮助中完全消失。

失败模式:test/presmoke/serviceprofiler_param_optimizer.py:50 明确使用 msserviceprofiler optimizer 命令,说明这三个子命令是真实可用的对外功能;旧实现(改动前)在无参数时调用 parser.print_help() 会动态列出全部子命令,因此这是本 diff 引入的用户可见帮助信息回归——用户无法从 --help 发现 optimizer/advisor/source_to_train。

建议:根入口(组合式 CLI,注册了 source_to_train/optimizer/advisor/analyze/parse/split/compare)不应使用面向 profiler 单包的静态 ROOT_HELP。建议让 create_parser() 支持传入与入口对应的帮助文本(或标志位),使组合式 CLI 的 --help/无参数帮助包含全部子命令;或者在完成子命令注册后再动态生成帮助。至少应在 ROOT_HELP 中补全 optimizer/advisor/source_to_train 的说明。

likedislike
不准确?
30- subparsers = parser.add_subparsers(help="sub-command help")26+ from ms_serviceparam_optimizer.ms_serviceparam_optimizer.optimizer import optimizer
27+ from ms_serviceparam_optimizer.ms_serviceparam_optimizer.train import source_to_train
28+ from ms_service_profiler import compare, split, analyze, parse
31 29 
32 source_to_train.arg_parse(subparsers)30 source_to_train.arg_parse(subparsers)
33 optimizer.arg_parse(subparsers)31 optimizer.arg_parse(subparsers)
34 advisor.arg_parse(subparsers)32 advisor.arg_parse(subparsers)
35 analyze.arg_parse(subparsers)33 analyze.arg_parse(subparsers)
34+ parse.arg_parse(subparsers)
36 split.arg_parse(subparsers)35 split.arg_parse(subparsers)
37 compare.arg_parse(subparsers)36 compare.arg_parse(subparsers)
38- args = parser.parse_args()37+ run_parser(parser)
39- 
40- # run
41- if hasattr(args, "func"):
42- args.func(args=args)
43- else:
44- parser.print_help()
45 38 
46 39 
47if __name__ == "__main__":40if __name__ == "__main__":
@@ -14,35 +14,37 @@
14# See the Mulan PSL v2 for more details.14# See the Mulan PSL v2 for more details.
15# -------------------------------------------------------------------------15# -------------------------------------------------------------------------
16 16 
17-import argparse
18from importlib.metadata import entry_points17from importlib.metadata import entry_points
19 18 
19+from ms_service_profiler.cli import create_subcommand_parser, run_parser
20+ 
20 21 
21def _load_entries():22def _load_entries():
22 eps = entry_points()23 eps = entry_points()
23 ep_group = 'ms_service_profiler_plugins'24 ep_group = 'ms_service_profiler_plugins'
24 plugin_eps = eps.select(group=ep_group)25 plugin_eps = eps.select(group=ep_group)
25 26 
26- yield from (ep.load() for ep in plugin_eps)27+ if plugin_eps:
28+ yield from (ep.load() for ep in plugin_eps)
29+ return
30+ 
31+ from ms_service_profiler import analyze, compare, parse, split
32+ 
33+ yield analyze.arg_parse
34+ yield compare.arg_parse
35+ yield parse.arg_parse
36+ yield split.arg_parse
27 37 
28 38 
29-def main(): 39+def main():
30- parser = argparse.ArgumentParser(40+ parser, subparsers = create_subcommand_parser()
31- formatter_class=argparse.ArgumentDefaultsHelpFormatter,41+ if subparsers is None:
32- description="[MindStudio] msserviceprofiler command line tool"42+ return
33- )
34- subparsers = parser.add_subparsers(help="sub-command help")
35 43 
36 for entry_fn in _load_entries():44 for entry_fn in _load_entries():
37 entry_fn(subparsers)45 entry_fn(subparsers)
38 46 
39- args = parser.parse_args()47+ run_parser(parser)
40- 
41- # run
42- if hasattr(args, "func"):
43- args.func(args=args)
44- else:
45- parser.print_help()
46 48 
47 49 
48if __name__ == "__main__":50if __name__ == "__main__":
@@ -0,0 +1,196 @@
1+# -------------------------------------------------------------------------
2+# This file is part of the MindStudio project.
3+# Copyright (c) 2026 Huawei Technologies Co.,Ltd.
4+#
5+# MindStudio is licensed under Mulan PSL v2.
6+# You can use this software according to the terms and conditions of the Mulan PSL v2.
7+# You may obtain a copy of Mulan PSL v2 at:
8+#
9+# http://license.coscl.org.cn/MulanPSL2
10+#
11+# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
12+# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
13+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
14+# See the Mulan PSL v2 for more details.
15+# -------------------------------------------------------------------------
16+ 
17+import argparse
18+import os
19+import sys
20+from importlib import metadata
21+from pathlib import Path
22+ 
23+ 
24+TOOL_NAME = "msserviceprofiler"
25+PACKAGE_NAME = "ms_service_profiler"
26+REPO_URL = "https://gitcode.com/Ascend/msserviceprofiler"
27+ 
28+ 
29+ROOT_HELP = """Description:
30+ Analyze, split, compare, and parse MindStudio Service Profiler data for
31+ service-oriented inference performance tuning.
32+ 
33+Usage:
34+ msserviceprofiler <command> [options]
35+ msserviceprofiler --help
36+ msserviceprofiler --version
37+ 
38+Commands:
39+ analyze Analyze service profiler data and export summary results
40+ parse Parse profiler data into db, csv, or json outputs
41+ split Split prefill/decode request data for targeted analysis
42+ compare Compare performance profiles between two runs
43+ 
44+Optional arguments:
45+ -h, --help Show this help message and exit
46+ -V, --version Show version information and exit
47+ 
48+Examples:
49+ # Parse profiler data into the default ./output directory
50+ msserviceprofiler parse --input-path ./profiling_data
51+ 
52+ # Analyze profiler data and save csv/json/db results
53+ msserviceprofiler analyze --input-path ./profiling_data --output-path ./output
54+ 
55+ # Compare two profiling runs
56+ msserviceprofiler compare ./profiling_data/before ./profiling_data/after
57+ 
58+Output:
59+ <output-path>/profiler.db
60+ <output-path>/*.csv
61+ <output-path>/*.json
62+ 
63+Troubleshooting:
64+ - Use 'msserviceprofiler <command> --help' to view command-specific options.
65+ - If input data is not found, check that --input-path points to the directory
66+ containing profiler output files.
67+ See also: docs/zh/msserviceprofiler_serving_tuning_instruct.md
68+"""
69+ 
70+ 
71+class RootHelpParser(argparse.ArgumentParser):
72+ def format_help(self):
73+ if self.prog == TOOL_NAME:
74+ return ROOT_HELP
75+ return super().format_help()
76+ 
77+ 
78+class VersionAction(argparse.Action):
79+ def __init__(self, option_strings, dest=argparse.SUPPRESS, default=argparse.SUPPRESS, **kwargs):
80+ help_text = kwargs.pop("help", None)
81+ super().__init__(
82+ option_strings=option_strings,
83+ dest=dest,
84+ nargs=0,
85+ default=default,
86+ help=help_text,
87+ )
88+ 
89+ def __call__(self, parser, namespace, values, option_string=None):
90+ parser._print_message(f"{get_version_text()}\n", sys.stdout)
91+ parser.exit()
92+ 
93+ 
94+def create_parser():
95+ parser = RootHelpParser(
96+ prog=TOOL_NAME,
97+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
98+ description="[MindStudio] msserviceprofiler command line tool",
99+ )
100+ parser.add_argument("-V", "--version", action=VersionAction, help="Show version information and exit")
101+ return parser
102+ 
103+ 
104+def is_root_cli_request(argv=None):
105+ argv = sys.argv[1:] if argv is None else argv
106+ return not argv or argv[0] in ("-h", "--help", "-V", "--version")
107+ 
108+ 
109+def create_subcommand_parser():
110+ parser = create_parser()
111+ if is_root_cli_request():
112+ run_parser(parser)
113+ return parser, None
114+ return parser, parser.add_subparsers(help="sub-command help")
115+ 
116+ 
117+def get_version_text():
118+ dependencies = _format_dependencies()
119+ if dependencies:
120+ dependencies = "\n\nDependencies:\n" + dependencies
121+ 
122+ return (
123+ "=================================================================\n"
124+ " >>>>> MindStudio <<<<<\n"
125+ " THE END-TO-END TOOLCHAIN TO UNLEASH HUAWEI ASCEND COMPUTE\n"
126+ "=================================================================\n"
127+ f"{TOOL_NAME} {_get_package_version()} ({_get_git_commit()})\n"
128+ "Copyright (C) 2026 Huawei Technologies Co., Ltd.\n"
129+ "License: Mulan PSL v2.\n\n"
130+ "Build Info:\n"
131+ f" Date : {_get_build_date()}\n"
132+ f" Repo : {REPO_URL}"
133+ f"{dependencies}"
134+ )
135+ 
136+ 
137+def _get_package_version():
138+ try:
139+ return metadata.version(PACKAGE_NAME)
140+ except metadata.PackageNotFoundError:
141+ return _read_pyproject_version()
142+ 
143+ 
144+def _read_pyproject_version():
145+ pyproject = Path(__file__).resolve().parents[1] / "pyproject.toml"
146+ try:
147+ for line in pyproject.read_text(encoding="utf-8").splitlines():
148+ stripped = line.strip()
149+ if stripped.startswith("version"):
150+ return stripped.split("=", 1)[1].strip().strip("\"'")
151+ except OSError:
152+ pass
153+ return "unknown"
154+ 
155+ 
156+def _get_git_commit():
157+ repo_dir = Path(__file__).resolve().parents[1]
158+ try:
159+ head = (repo_dir / ".git" / "HEAD").read_text(encoding="utf-8").strip()
160+ if head.startswith("ref:"):
161+ ref_path = repo_dir / ".git" / head.split(" ", 1)[1]
162+ commit = ref_path.read_text(encoding="utf-8").strip()
163+ else:
164+ commit = head
165+ except (IndexError, OSError):
166+ return "unknown"
167+ return commit[:12] if len(commit) >= 7 else "unknown"
168+ 
169+ 
170+def _get_build_date():
171+ return os.environ.get("MS_SERVICE_PROFILER_BUILD_DATE", "unknown")
172+ 
173+ 
174+def _format_dependencies():
175+ deps = [
176+ ("Pandas", "pandas"),
177+ ("NumPy", "numpy"),
178+ ("OpenTelemetry", "opentelemetry-api"),
179+ ("PyYAML", "PyYAML"),
180+ ]
181+ lines = []
182+ for display_name, package_name in deps:
183+ try:
184+ dep_version = metadata.version(package_name)
185+ except metadata.PackageNotFoundError:
186+ continue
187+ lines.append(f" {display_name:<13}: {dep_version}")
188+ return "\n".join(lines)
189+ 
190+ 
191+def run_parser(parser):
192+ args = parser.parse_args()
193+ if hasattr(args, "func"):
194+ args.func(args=args)
195+ return
196+ parser.print_help(sys.stdout)
@@ -0,0 +1,52 @@
1+import pytest
2+ 
3+from ms_service_profiler.cli import ROOT_HELP, create_parser, get_version_text, run_parser
4+ 
5+ 
6+def test_root_help_uses_unified_sections(capsys):
7+ parser = create_parser()
8+ 
9+ with pytest.raises(SystemExit) as exc_info:
10+ parser.parse_args(["--help"])
11+ 
12+ assert exc_info.value.code == 0
13+ output = capsys.readouterr().out
14+ assert "Description:" in output
15+ assert "Usage:" in output
16+ assert "Commands:" in output
17+ assert "Examples:" in output
18+ assert "Troubleshooting:" in output
19+ assert "msserviceprofiler --version" in output
20+ 
21+ 
22+def test_root_version_uses_mindstudio_format():
23+ version_text = get_version_text()
24+ 
25+ assert ">>>>> MindStudio <<<<<" in version_text
26+ assert "msserviceprofiler" in version_text
27+ assert "Copyright (C) 2026 Huawei Technologies Co., Ltd." in version_text
28+ assert "License: Mulan PSL v2." in version_text
29+ assert "Build Info:" in version_text
30+ assert "Repo : https://gitcode.com/Ascend/msserviceprofiler" in version_text
31+ 
32+ 
33+def test_root_version_action_preserves_multiline_output(capsys):
34+ parser = create_parser()
35+ 
36+ with pytest.raises(SystemExit) as exc_info:
37+ parser.parse_args(["--version"])
38+ 
39+ assert exc_info.value.code == 0
40+ output = capsys.readouterr().out
41+ assert "=================================================================\n" in output
42+ assert "\nBuild Info:\n" in output
43+ assert "\n Repo : https://gitcode.com/Ascend/msserviceprofiler" in output
44+ 
45+ 
46+def test_run_parser_without_args_prints_root_help(monkeypatch, capsys):
47+ parser = create_parser()
48+ monkeypatch.setattr("sys.argv", ["msserviceprofiler"])
49+ 
50+ run_parser(parser)
51+ 
52+ assert capsys.readouterr().out == ROOT_HELP