While the telemetry infrastructure supported using "/help,/<cmd>" to get help on specific commands, with the addition of script-specific commands, we needed better help support to explain the use of FOREACH, and to allow e.g. "help /<cmd>" using space separation, which is more intuitive. This patch adds that help support.
Signed-off-by: Bruce Richardson <[email protected]> --- v2: added "help aliases" to list defined aliases updated docs for expanded help command --- doc/guides/howto/telemetry.rst | 31 ++++++++++++--- usertools/dpdk-telemetry.py | 69 +++++++++++++++++++++++++++++++++- 2 files changed, 93 insertions(+), 7 deletions(-) diff --git a/doc/guides/howto/telemetry.rst b/doc/guides/howto/telemetry.rst index bdefbdc6a6..00cfc1a1e1 100644 --- a/doc/guides/howto/telemetry.rst +++ b/doc/guides/howto/telemetry.rst @@ -81,12 +81,31 @@ and query information using the telemetry client python script. ... "tx_priority7_xon_to_xoff_packets": 0}} - * Get the help text for a command. This will indicate what parameters are - required. Pass the command as a parameter:: - - --> /help,/ethdev/xstats - {"/help": {"/ethdev/xstats": "Returns the extended stats for a port. - Parameters: int port_id"}} + * Get the help text for a command. + This will indicate what parameters are required. + Use the ``help`` keyword followed by the command or keyword of interest, + for example:: + + --> help FOREACH + FOREACH usage: + FOREACH /<list_cmd> /<iter_cmd> .<field> [.<field> ...] + FOREACH <var> /<list_cmd> /<iter_cmd_with_$var> .<field> [.<field> ...] + + Examples: + FOREACH /ethdev/list /ethdev/stats .opackets + ... + + --> help /ethdev/xstats + {"/help": {"/ethdev/xstats": "Returns the extended stats for a port. Parameters: int port_id"}} + + .. Note:: + For commands starting with ``/`` that are telemetry enpoints, + the help text can also be obtained by sending the ``/help`` command to the telemetry socket. + In this case, the parameter must be separated by a comma, not a space. + For example:: + + --> /help,/ethdev/xstats + {"/help": {"/ethdev/xstats": "Returns the extended stats for a port. Parameters: int port_id"}} * Run a compound query using ``FOREACH``. diff --git a/usertools/dpdk-telemetry.py b/usertools/dpdk-telemetry.py index 7a25b78730..20627b596b 100755 --- a/usertools/dpdk-telemetry.py +++ b/usertools/dpdk-telemetry.py @@ -25,6 +25,26 @@ ALIAS_FILE = ".dpdk_telemetry_aliases" MAX_ALIAS_EXPANSIONS = 32 +BASIC_HELP_TEXT = """Basic usage: + /<command>[,<params>] Send a telemetry command to the app + FOREACH ... Run a compound query over list items + help Show this help + help /<command> Show app-provided help for a command + help FOREACH Show FOREACH usage and examples + quit Exit the client +""" + +FOREACH_HELP_TEXT = """FOREACH usage: + FOREACH /<list_cmd> /<iter_cmd> .<field> [.<field> ...] + FOREACH <var> /<list_cmd> /<iter_cmd_with_$var> .<field> [.<field> ...] + +Examples: + FOREACH /ethdev/list /ethdev/stats .opackets + FOREACH /ethdev/list /ethdev/stats .ipackets .opackets + FOREACH i /ethdev/list /ethdev/info,$i .name + FOREACH i /ethdev/list /ethdev/stats,$i .ipackets .opackets +""" + def load_aliases(alias_path=None): """Load aliases from $HOME/.dpdk_telemetry_aliases or a custom path if provided""" @@ -209,10 +229,57 @@ def handle_foreach(sock, output_buf_len, text, pretty=False): print(json.dumps(output, indent=indent)) +def command_exists(cmd): + """Check if a telemetry command exists in the command list""" + return cmd in CMDS + + +def app_help_command_for(target_cmd): + """Build a '/help,<command>' query for app-side command help""" + if not target_cmd: + return None + normalized = target_cmd.strip() + if not normalized.startswith("/"): + return None + if not command_exists(normalized): + print("Unknown command for help: {}".format(normalized)) + return None + return "/help,{}".format(normalized) + + +def handle_user_help(sock, output_buf_len, text, pretty=False): + """Handle local 'help' command and command-specific help lookup""" + parts = text.split(None, 1) + if len(parts) == 1: + print(BASIC_HELP_TEXT, end="") + return + + help_arg = parts[1].strip() + if help_arg.upper() == "FOREACH": + print(FOREACH_HELP_TEXT, end="") + return + elif help_arg.lower() == "alias" or help_arg.lower() == "aliases": + if not ALIASES: + print("No aliases defined") + return + print("Defined aliases:") + for name, command in ALIASES.items(): + print(f" {name}='{command}'") + return + + cmd = app_help_command_for(help_arg) + if cmd is None: + print("Usage: help [FOREACH|/<command>]") + return + send_command(sock, cmd, output_buf_len, echo=True, pretty=pretty) + + def handle_command(sock, output_buf_len, text, pretty=False): """Execute a user command if recognized""" if text.startswith("/"): send_command(sock, text, output_buf_len, echo=True, pretty=pretty) + elif text == "help" or text.startswith("help "): + handle_user_help(sock, output_buf_len, text, pretty) elif text.startswith("FOREACH "): handle_foreach(sock, output_buf_len, text, pretty) @@ -346,7 +413,7 @@ def handle_socket(args, path): def readline_complete(text, state): """Find any matching commands from the list based on user input""" - all_cmds = ["quit"] + list(ALIASES.keys()) + CMDS + all_cmds = ["quit", "help"] + list(ALIASES.keys()) + CMDS if text: matches = [c for c in all_cmds if c.startswith(text)] else: -- 2.53.0

