* [PATCH 0/2] extend interactive telemetry script
@ 2026-05-21 15:39 Bruce Richardson
2026-05-21 15:39 ` [PATCH 1/2] usertools/telemetry: add a FOREACH command Bruce Richardson
` (4 more replies)
0 siblings, 5 replies; 18+ messages in thread
From: Bruce Richardson @ 2026-05-21 15:39 UTC (permalink / raw)
To: dev; +Cc: Bruce Richardson
To simplify interactive telemetry script for general use, i.e. not from
other scripts, we can add two new features to it:
1. Support for FOREACH to allow gathering a set of output values across
a list of ports or devices, e.g. ethdevs or rawdevs.
2. Support having predefined aliases in a file in the user's home
directory to simplify the use of more complicated FOREACH commands.
Putting these together, we can create new commands such as "eth_names".
bruce@host:$ cat ~/.dpdk_telemetry_aliases
eth_names=FOREACH index /ethdev/list /ethdev/info,$index .name
bruce@host:$ echo eth_names | ./usertools/dpdk-telemetry.py | jq
[
{
"index": 0,
"name": "0000:16:00.0"
},
{
"index": 1,
"name": "0000:16:00.1"
}
]
Bruce Richardson (2):
usertools/telemetry: add a FOREACH command
usertools/telemetry: support using aliases for long commands
doc/guides/howto/telemetry.rst | 76 +++++++++++++
usertools/dpdk-telemetry.py | 201 ++++++++++++++++++++++++++++++++-
2 files changed, 271 insertions(+), 6 deletions(-)
--
2.53.0
^ permalink raw reply [flat|nested] 18+ messages in thread* [PATCH 1/2] usertools/telemetry: add a FOREACH command 2026-05-21 15:39 [PATCH 0/2] extend interactive telemetry script Bruce Richardson @ 2026-05-21 15:39 ` Bruce Richardson 2026-05-21 15:39 ` [PATCH 2/2] usertools/telemetry: support using aliases for long commands Bruce Richardson ` (3 subsequent siblings) 4 siblings, 0 replies; 18+ messages in thread From: Bruce Richardson @ 2026-05-21 15:39 UTC (permalink / raw) To: dev; +Cc: Bruce Richardson To simplify querying data from multiple devices, e.g. across ethdevs, or dmadevs, add a FOREACH command to the python script, allowing you to run, e.g. /ethdev/list, and then run a second command for each item in the list, gathering the relevant output values, optionally including an index counter. Simple examples are given in the documentation: --> FOREACH /ethdev/list /ethdev/stats .opackets [0, 0] --> FOREACH /ethdev/list /ethdev/stats .ipackets .opackets [{"ipackets": 0, "opackets": 0}, {"ipackets": 0, "opackets": 0}] --> FOREACH i /ethdev/list /ethdev/info,$i .name [{"i": 0, "name": "0000:16:00.0"}, {"i": 1, "name": "0000:16:00.1"}] --> FOREACH i /ethdev/list /ethdev/stats,$i .ipackets .opackets [{"i": 0, "ipackets": 0, "opackets": 0}, {"i": 1, "ipackets": 0, "opackets": 0}] Signed-off-by: Bruce Richardson <bruce.richardson@intel.com> --- doc/guides/howto/telemetry.rst | 42 +++++++++++ usertools/dpdk-telemetry.py | 128 ++++++++++++++++++++++++++++++++- 2 files changed, 167 insertions(+), 3 deletions(-) diff --git a/doc/guides/howto/telemetry.rst b/doc/guides/howto/telemetry.rst index 0464c431fe..4bf48c635e 100644 --- a/doc/guides/howto/telemetry.rst +++ b/doc/guides/howto/telemetry.rst @@ -88,6 +88,48 @@ and query information using the telemetry client python script. {"/help": {"/ethdev/xstats": "Returns the extended stats for a port. Parameters: int port_id"}} + * Run a compound query using ``FOREACH``. + + The ``FOREACH`` command runs a list command, iterates each returned item, + runs a second command for each item, and emits combined JSON output. + + Start with the simplest form (no loop variable):: + + FOREACH /<list_cmd> /<iter_cmd> .<field> [.<field> ...] + + To include numbered output, use a loop variable:: + + FOREACH <var> /<list_cmd> /<iter_cmd_with_$var> .<field> [.<field> ...] + + Notes: + + - Field selectors are whitespace-separated tokens, each starting with ``.``. + - In no-variable mode, the iter command is called as ``/<iter_cmd>,<item>``. + - In loop-variable mode, use ``$<var>`` in the iter command where the + item value should be substituted. + + Examples:: + + --> FOREACH /ethdev/list /ethdev/stats .opackets + [0, 0] + + --> FOREACH /ethdev/list /ethdev/stats .ipackets .opackets + [{"ipackets": 0, "opackets": 0}, {"ipackets": 0, "opackets": 0}] + + --> FOREACH i /ethdev/list /ethdev/info,$i .name + [{"i": 0, "name": "0000:16:00.0"}, {"i": 1, "name": "0000:16:00.1"}] + + --> FOREACH i /ethdev/list /ethdev/stats,$i .ipackets .opackets + [{"i": 0, "ipackets": 0, "opackets": 0}, {"i": 1, "ipackets": 0, "opackets": 0}] + + Output behavior: + + - Without loop variable and one field: returns an array of values. + - Without loop variable and multiple fields: returns an array of objects + containing named value fields. + - With loop variable: returns an array of objects containing the loop + variable field and requested value fields. + Connecting to Different DPDK Processes -------------------------------------- diff --git a/usertools/dpdk-telemetry.py b/usertools/dpdk-telemetry.py index 09258a1f7e..2de10cff69 100755 --- a/usertools/dpdk-telemetry.py +++ b/usertools/dpdk-telemetry.py @@ -23,6 +23,130 @@ CMDS = [] +def send_command(sock, cmd, output_buf_len, echo=False, pretty=False): + """Send a telemetry command and return the parsed JSON reply""" + sock.send(cmd.encode()) + return read_socket(sock, output_buf_len, echo, pretty) + + +def get_cmd_payload(reply, cmd): + """Return the payload for a command response if present""" + if isinstance(reply, dict) and len(reply) == 1: + return next(iter(reply.values())) + return None + + +def get_path_value(payload, path): + """Resolve a dotted path (e.g. '.name' or '.a.b') from a JSON payload""" + if not path: + return payload + + keys = [k for k in path.lstrip(".").split(".") if k] + val = payload + for key in keys: + if not isinstance(val, dict) or key not in val: + return None + val = val[key] + return val + + +def parse_selectors(selector_text): + """Parse whitespace-separated dotted selectors""" + selectors = selector_text.split() + if not selectors: + print("Invalid FOREACH syntax: missing selector") + return None + if any(not selector.startswith(".") for selector in selectors): + print("Invalid FOREACH syntax: selector must start with '.'") + return None + return selectors + + +def parse_foreach(text): + """Parse FOREACH [<var>] /<cmd> /<parameterized cmd> .<value> [.<value> ...]""" + try: + tokens = text.split(None, 3) + except ValueError: + print("Invalid FOREACH syntax") + return None + + if len(tokens) != 4: + print("Invalid FOREACH syntax") + return None + + _, arg1, arg2, arg3 = tokens + if arg1.startswith("/"): + var_name = None + list_cmd = arg1 + iter_cmd = arg2 + selector_text = arg3 + else: + var_name = arg1 + list_cmd = arg2 + try: + iter_cmd, selector_text = arg3.split(None, 1) + except ValueError: + print("Invalid FOREACH syntax") + return None + + if not list_cmd.startswith("/") or not iter_cmd.startswith("/"): + print("Invalid FOREACH syntax: commands must start with '/'") + return None + + selectors = parse_selectors(selector_text) + if selectors is None: + return None + + return var_name, list_cmd, iter_cmd, selectors + + +def build_foreach_result(item, var_name, payload, selectors): + """Build one FOREACH result entry based on selector count and index mode""" + values = {selector.lstrip("."): get_path_value(payload, selector) for selector in selectors} + + if var_name is None and len(selectors) == 1: + return next(iter(values.values())) + if var_name is None: + return values + + return {var_name: item, **values} + + +def handle_foreach(sock, output_buf_len, text, pretty=False): + """Handle FOREACH queries and print telemetry-like JSON array output""" + parsed = parse_foreach(text) + if parsed is None: + return + var_name, list_cmd, iter_cmd, selectors = parsed + + list_reply = send_command(sock, list_cmd, output_buf_len) + values = get_cmd_payload(list_reply, list_cmd) + if not isinstance(values, list): + print("FOREACH source command did not return a JSON array") + return + + output = [] + for item in values: + if var_name is None: + cmd = "{},{}".format(iter_cmd, item) + else: + cmd = iter_cmd.replace("$" + var_name, str(item)) + item_reply = send_command(sock, cmd, output_buf_len) + item_payload = get_cmd_payload(item_reply, cmd) + output.append(build_foreach_result(item, var_name, item_payload, selectors)) + + indent = 2 if pretty else None + print(json.dumps(output, indent=indent)) + + +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.startswith("FOREACH "): + handle_foreach(sock, output_buf_len, text, pretty) + + def read_socket(sock, buf_len, echo=True, pretty=False): """Read data from socket and return it in JSON format""" reply = sock.recv(buf_len).decode() @@ -140,9 +264,7 @@ def handle_socket(args, path): try: text = input(prompt).strip() while text != "quit": - if text.startswith("/"): - sock.send(text.encode()) - read_socket(sock, output_buf_len, pretty=prompt) + handle_command(sock, output_buf_len, text, pretty=prompt) text = input(prompt).strip() except EOFError: pass -- 2.53.0 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 2/2] usertools/telemetry: support using aliases for long commands 2026-05-21 15:39 [PATCH 0/2] extend interactive telemetry script Bruce Richardson 2026-05-21 15:39 ` [PATCH 1/2] usertools/telemetry: add a FOREACH command Bruce Richardson @ 2026-05-21 15:39 ` Bruce Richardson 2026-05-22 0:44 ` [PATCH 0/2] extend interactive telemetry script fengchengwen ` (2 subsequent siblings) 4 siblings, 0 replies; 18+ messages in thread From: Bruce Richardson @ 2026-05-21 15:39 UTC (permalink / raw) To: dev; +Cc: Bruce Richardson Similarly to how shell aliases work, allow specifying of short alias commands for dpdk-telemetry.py script. The aliases are read from "$HOME/.dpdk_telemetry_aliases" at startup. Some examples of use from the docs. Alias file contents: # Basic shortcuts ls=/ethdev/list names=FOREACH i /ethdev/list /ethdev/info,$i .name q=quit Alias use: --> ls {"/ethdev/list": [0, 1]} --> names [{"i": 0, "name": "0000: Signed-off-by: Bruce Richardson <bruce.richardson@intel.com> --- doc/guides/howto/telemetry.rst | 34 +++++++++++++++ usertools/dpdk-telemetry.py | 75 ++++++++++++++++++++++++++++++++-- 2 files changed, 105 insertions(+), 4 deletions(-) diff --git a/doc/guides/howto/telemetry.rst b/doc/guides/howto/telemetry.rst index 4bf48c635e..072289aec8 100644 --- a/doc/guides/howto/telemetry.rst +++ b/doc/guides/howto/telemetry.rst @@ -130,6 +130,40 @@ and query information using the telemetry client python script. - With loop variable: returns an array of objects containing the loop variable field and requested value fields. + * Use command aliases. + + The telemetry script can load aliases at startup from:: + + $HOME/.dpdk_telemetry_aliases + + Each alias entry must be in ``alias=command`` format. + Empty lines and lines starting with ``#`` are ignored. + + Example alias file:: + + # Basic shortcuts + ls=/ethdev/list + names=FOREACH i /ethdev/list /ethdev/info,$i .name + q=quit + + Alias behavior is intentionally similar to shell aliases: + + - The first token of the entered input is checked for an alias match. + - If matched, that first token is replaced with its expansion. + - Alias expansion is recursive (aliases can expand to other aliases). + - Expansion has a safety limit to prevent infinite loops. + + Examples:: + + --> ls + {"/ethdev/list": [0, 1]} + + --> names + [{"i": 0, "name": "0000:16:00.0"}, {"i": 1, "name": "0000:16:00.1"}] + + --> q + # exits the client + Connecting to Different DPDK Processes -------------------------------------- diff --git a/usertools/dpdk-telemetry.py b/usertools/dpdk-telemetry.py index 2de10cff69..8b976160e0 100755 --- a/usertools/dpdk-telemetry.py +++ b/usertools/dpdk-telemetry.py @@ -21,6 +21,70 @@ SOCKET_NAME = "dpdk_telemetry.{}".format(TELEMETRY_VERSION) DEFAULT_PREFIX = "rte" CMDS = [] +ALIASES = {} +ALIAS_FILE = ".dpdk_telemetry_aliases" +MAX_ALIAS_EXPANSIONS = 32 + + +def load_aliases(): + """Load aliases from $HOME/.dpdk_telemetry_aliases""" + aliases = {} + home = os.environ.get("HOME") + if not home: + return aliases + + alias_path = os.path.join(home, ALIAS_FILE) + if not os.path.isfile(alias_path): + return aliases + + try: + with open(alias_path) as alias_file: + for line_num, line in enumerate(alias_file, start=1): + entry = line.strip() + if not entry or entry.startswith("#"): + continue + if "=" not in entry: + print( + "Warning: ignoring malformed alias at {}:{}".format(alias_path, line_num), + file=sys.stderr, + ) + continue + name, command = entry.split("=", 1) + name = name.strip() + command = command.strip() + if not name or not command: + print( + "Warning: ignoring malformed alias at {}:{}".format(alias_path, line_num), + file=sys.stderr, + ) + continue + aliases[name] = command + except OSError as e: + print("Warning: failed to read {}: {}".format(alias_path, e), file=sys.stderr) + + return aliases + + +def expand_aliases(text, aliases): + """Expand aliases similarly to shell aliases on the first token""" + expanded = text + for _ in range(MAX_ALIAS_EXPANSIONS): + stripped = expanded.lstrip() + if not stripped: + return expanded + + parts = stripped.split(None, 1) + first = parts[0] + rest = parts[1] if len(parts) > 1 else "" + + if first not in aliases: + return expanded + + alias_value = aliases[first] + expanded = "{} {}".format(alias_value, rest).strip() if rest else alias_value + + print("Warning: alias expansion limit reached", file=sys.stderr) + return expanded def send_command(sock, cmd, output_buf_len, echo=False, pretty=False): @@ -262,10 +326,12 @@ def handle_socket(args, path): # interactive prompt try: - text = input(prompt).strip() - while text != "quit": - handle_command(sock, output_buf_len, text, pretty=prompt) + while True: text = input(prompt).strip() + expanded = expand_aliases(text, ALIASES) + if expanded == "quit": + break + handle_command(sock, output_buf_len, expanded, pretty=prompt) except EOFError: pass finally: @@ -274,7 +340,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"] + CMDS + all_cmds = ["quit"] + list(ALIASES.keys()) + CMDS if text: matches = [c for c in all_cmds if c.startswith(text)] else: @@ -304,6 +370,7 @@ def readline_complete(text, state): help="List all possible file-prefixes and exit", ) args = parser.parse_args() +ALIASES = load_aliases() if args.list: list_fp() sys.exit(0) -- 2.53.0 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* Re: [PATCH 0/2] extend interactive telemetry script 2026-05-21 15:39 [PATCH 0/2] extend interactive telemetry script Bruce Richardson 2026-05-21 15:39 ` [PATCH 1/2] usertools/telemetry: add a FOREACH command Bruce Richardson 2026-05-21 15:39 ` [PATCH 2/2] usertools/telemetry: support using aliases for long commands Bruce Richardson @ 2026-05-22 0:44 ` fengchengwen 2026-05-22 7:51 ` Bruce Richardson 2026-05-22 13:37 ` [PATCH v2 0/3] " Bruce Richardson 2026-06-09 16:13 ` [PATCH v3 0/3] extend interactive telemetry script Bruce Richardson 4 siblings, 1 reply; 18+ messages in thread From: fengchengwen @ 2026-05-22 0:44 UTC (permalink / raw) To: Bruce Richardson, dev On 5/21/2026 11:39 PM, Bruce Richardson wrote: > To simplify interactive telemetry script for general use, i.e. not from > other scripts, we can add two new features to it: > > 1. Support for FOREACH to allow gathering a set of output values across > a list of ports or devices, e.g. ethdevs or rawdevs. > 2. Support having predefined aliases in a file in the user's home > directory to simplify the use of more complicated FOREACH commands. +1 How about add help command to introducing above FOREACH and aliases? > > Putting these together, we can create new commands such as "eth_names". > > bruce@host:$ cat ~/.dpdk_telemetry_aliases > eth_names=FOREACH index /ethdev/list /ethdev/info,$index .name > > bruce@host:$ echo eth_names | ./usertools/dpdk-telemetry.py | jq > [ > { > "index": 0, > "name": "0000:16:00.0" > }, > { > "index": 1, > "name": "0000:16:00.1" > } > ] > > Bruce Richardson (2): > usertools/telemetry: add a FOREACH command > usertools/telemetry: support using aliases for long commands > > doc/guides/howto/telemetry.rst | 76 +++++++++++++ > usertools/dpdk-telemetry.py | 201 ++++++++++++++++++++++++++++++++- > 2 files changed, 271 insertions(+), 6 deletions(-) > > -- > 2.53.0 > ^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 0/2] extend interactive telemetry script 2026-05-22 0:44 ` [PATCH 0/2] extend interactive telemetry script fengchengwen @ 2026-05-22 7:51 ` Bruce Richardson 0 siblings, 0 replies; 18+ messages in thread From: Bruce Richardson @ 2026-05-22 7:51 UTC (permalink / raw) To: fengchengwen; +Cc: dev On Fri, May 22, 2026 at 08:44:55AM +0800, fengchengwen wrote: > On 5/21/2026 11:39 PM, Bruce Richardson wrote: > > To simplify interactive telemetry script for general use, i.e. not from > > other scripts, we can add two new features to it: > > > > 1. Support for FOREACH to allow gathering a set of output values across > > a list of ports or devices, e.g. ethdevs or rawdevs. > > 2. Support having predefined aliases in a file in the user's home > > directory to simplify the use of more complicated FOREACH commands. > > +1 > How about add help command to introducing above FOREACH and aliases? > Sure, not a bad idea if we are extending the script to make it more powerful. Will add in a V2. /Bruce ^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v2 0/3] extend interactive telemetry script 2026-05-21 15:39 [PATCH 0/2] extend interactive telemetry script Bruce Richardson ` (2 preceding siblings ...) 2026-05-22 0:44 ` [PATCH 0/2] extend interactive telemetry script fengchengwen @ 2026-05-22 13:37 ` Bruce Richardson 2026-05-22 13:37 ` [PATCH v2 1/3] usertools/telemetry: add a FOREACH command Bruce Richardson ` (2 more replies) 2026-06-09 16:13 ` [PATCH v3 0/3] extend interactive telemetry script Bruce Richardson 4 siblings, 3 replies; 18+ messages in thread From: Bruce Richardson @ 2026-05-22 13:37 UTC (permalink / raw) To: dev; +Cc: fengchengwen, Bruce Richardson To simplify interactive telemetry script for general use, i.e. not from other scripts, we can add two new features to it: 1. Support for FOREACH to allow gathering a set of output values across a list of ports or devices, e.g. ethdevs or rawdevs. 2. Support having predefined aliases in a file in the user's home directory to simplify the use of more complicated FOREACH commands. Putting these together, we can create new commands such as "eth_names". bruce@host:$ cat ~/.dpdk_telemetry_aliases eth_names=FOREACH index /ethdev/list /ethdev/info,$index .name bruce@host:$ echo eth_names | ./usertools/dpdk-telemetry.py | jq [ { "index": 0, "name": "0000:16:00.0" }, { "index": 1, "name": "0000:16:00.1" } ] --- v2: added third patch with "help" command giving more details on how to use the various commands. Bruce Richardson (3): usertools/telemetry: add a FOREACH command usertools/telemetry: support using aliases for long commands usertools/telemetry: add help support doc/guides/howto/telemetry.rst | 76 ++++++++++ usertools/dpdk-telemetry.py | 260 ++++++++++++++++++++++++++++++++- 2 files changed, 330 insertions(+), 6 deletions(-) -- 2.53.0 ^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v2 1/3] usertools/telemetry: add a FOREACH command 2026-05-22 13:37 ` [PATCH v2 0/3] " Bruce Richardson @ 2026-05-22 13:37 ` Bruce Richardson 2026-05-29 0:17 ` fengchengwen 2026-05-22 13:37 ` [PATCH v2 2/3] usertools/telemetry: support using aliases for long commands Bruce Richardson 2026-05-22 13:37 ` [PATCH v2 3/3] usertools/telemetry: add help support Bruce Richardson 2 siblings, 1 reply; 18+ messages in thread From: Bruce Richardson @ 2026-05-22 13:37 UTC (permalink / raw) To: dev; +Cc: fengchengwen, Bruce Richardson To simplify querying data from multiple devices, e.g. across ethdevs, or dmadevs, add a FOREACH command to the python script, allowing you to run, e.g. /ethdev/list, and then run a second command for each item in the list, gathering the relevant output values, optionally including an index counter. Simple examples are given in the documentation: --> FOREACH /ethdev/list /ethdev/stats .opackets [0, 0] --> FOREACH /ethdev/list /ethdev/stats .ipackets .opackets [{"ipackets": 0, "opackets": 0}, {"ipackets": 0, "opackets": 0}] --> FOREACH i /ethdev/list /ethdev/info,$i .name [{"i": 0, "name": "0000:16:00.0"}, {"i": 1, "name": "0000:16:00.1"}] --> FOREACH i /ethdev/list /ethdev/stats,$i .ipackets .opackets [{"i": 0, "ipackets": 0, "opackets": 0}, {"i": 1, "ipackets": 0, "opackets": 0}] Signed-off-by: Bruce Richardson <bruce.richardson@intel.com> --- doc/guides/howto/telemetry.rst | 42 +++++++++++ usertools/dpdk-telemetry.py | 128 ++++++++++++++++++++++++++++++++- 2 files changed, 167 insertions(+), 3 deletions(-) diff --git a/doc/guides/howto/telemetry.rst b/doc/guides/howto/telemetry.rst index 0464c431fe..4bf48c635e 100644 --- a/doc/guides/howto/telemetry.rst +++ b/doc/guides/howto/telemetry.rst @@ -88,6 +88,48 @@ and query information using the telemetry client python script. {"/help": {"/ethdev/xstats": "Returns the extended stats for a port. Parameters: int port_id"}} + * Run a compound query using ``FOREACH``. + + The ``FOREACH`` command runs a list command, iterates each returned item, + runs a second command for each item, and emits combined JSON output. + + Start with the simplest form (no loop variable):: + + FOREACH /<list_cmd> /<iter_cmd> .<field> [.<field> ...] + + To include numbered output, use a loop variable:: + + FOREACH <var> /<list_cmd> /<iter_cmd_with_$var> .<field> [.<field> ...] + + Notes: + + - Field selectors are whitespace-separated tokens, each starting with ``.``. + - In no-variable mode, the iter command is called as ``/<iter_cmd>,<item>``. + - In loop-variable mode, use ``$<var>`` in the iter command where the + item value should be substituted. + + Examples:: + + --> FOREACH /ethdev/list /ethdev/stats .opackets + [0, 0] + + --> FOREACH /ethdev/list /ethdev/stats .ipackets .opackets + [{"ipackets": 0, "opackets": 0}, {"ipackets": 0, "opackets": 0}] + + --> FOREACH i /ethdev/list /ethdev/info,$i .name + [{"i": 0, "name": "0000:16:00.0"}, {"i": 1, "name": "0000:16:00.1"}] + + --> FOREACH i /ethdev/list /ethdev/stats,$i .ipackets .opackets + [{"i": 0, "ipackets": 0, "opackets": 0}, {"i": 1, "ipackets": 0, "opackets": 0}] + + Output behavior: + + - Without loop variable and one field: returns an array of values. + - Without loop variable and multiple fields: returns an array of objects + containing named value fields. + - With loop variable: returns an array of objects containing the loop + variable field and requested value fields. + Connecting to Different DPDK Processes -------------------------------------- diff --git a/usertools/dpdk-telemetry.py b/usertools/dpdk-telemetry.py index 09258a1f7e..2de10cff69 100755 --- a/usertools/dpdk-telemetry.py +++ b/usertools/dpdk-telemetry.py @@ -23,6 +23,130 @@ CMDS = [] +def send_command(sock, cmd, output_buf_len, echo=False, pretty=False): + """Send a telemetry command and return the parsed JSON reply""" + sock.send(cmd.encode()) + return read_socket(sock, output_buf_len, echo, pretty) + + +def get_cmd_payload(reply, cmd): + """Return the payload for a command response if present""" + if isinstance(reply, dict) and len(reply) == 1: + return next(iter(reply.values())) + return None + + +def get_path_value(payload, path): + """Resolve a dotted path (e.g. '.name' or '.a.b') from a JSON payload""" + if not path: + return payload + + keys = [k for k in path.lstrip(".").split(".") if k] + val = payload + for key in keys: + if not isinstance(val, dict) or key not in val: + return None + val = val[key] + return val + + +def parse_selectors(selector_text): + """Parse whitespace-separated dotted selectors""" + selectors = selector_text.split() + if not selectors: + print("Invalid FOREACH syntax: missing selector") + return None + if any(not selector.startswith(".") for selector in selectors): + print("Invalid FOREACH syntax: selector must start with '.'") + return None + return selectors + + +def parse_foreach(text): + """Parse FOREACH [<var>] /<cmd> /<parameterized cmd> .<value> [.<value> ...]""" + try: + tokens = text.split(None, 3) + except ValueError: + print("Invalid FOREACH syntax") + return None + + if len(tokens) != 4: + print("Invalid FOREACH syntax") + return None + + _, arg1, arg2, arg3 = tokens + if arg1.startswith("/"): + var_name = None + list_cmd = arg1 + iter_cmd = arg2 + selector_text = arg3 + else: + var_name = arg1 + list_cmd = arg2 + try: + iter_cmd, selector_text = arg3.split(None, 1) + except ValueError: + print("Invalid FOREACH syntax") + return None + + if not list_cmd.startswith("/") or not iter_cmd.startswith("/"): + print("Invalid FOREACH syntax: commands must start with '/'") + return None + + selectors = parse_selectors(selector_text) + if selectors is None: + return None + + return var_name, list_cmd, iter_cmd, selectors + + +def build_foreach_result(item, var_name, payload, selectors): + """Build one FOREACH result entry based on selector count and index mode""" + values = {selector.lstrip("."): get_path_value(payload, selector) for selector in selectors} + + if var_name is None and len(selectors) == 1: + return next(iter(values.values())) + if var_name is None: + return values + + return {var_name: item, **values} + + +def handle_foreach(sock, output_buf_len, text, pretty=False): + """Handle FOREACH queries and print telemetry-like JSON array output""" + parsed = parse_foreach(text) + if parsed is None: + return + var_name, list_cmd, iter_cmd, selectors = parsed + + list_reply = send_command(sock, list_cmd, output_buf_len) + values = get_cmd_payload(list_reply, list_cmd) + if not isinstance(values, list): + print("FOREACH source command did not return a JSON array") + return + + output = [] + for item in values: + if var_name is None: + cmd = "{},{}".format(iter_cmd, item) + else: + cmd = iter_cmd.replace("$" + var_name, str(item)) + item_reply = send_command(sock, cmd, output_buf_len) + item_payload = get_cmd_payload(item_reply, cmd) + output.append(build_foreach_result(item, var_name, item_payload, selectors)) + + indent = 2 if pretty else None + print(json.dumps(output, indent=indent)) + + +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.startswith("FOREACH "): + handle_foreach(sock, output_buf_len, text, pretty) + + def read_socket(sock, buf_len, echo=True, pretty=False): """Read data from socket and return it in JSON format""" reply = sock.recv(buf_len).decode() @@ -140,9 +264,7 @@ def handle_socket(args, path): try: text = input(prompt).strip() while text != "quit": - if text.startswith("/"): - sock.send(text.encode()) - read_socket(sock, output_buf_len, pretty=prompt) + handle_command(sock, output_buf_len, text, pretty=prompt) text = input(prompt).strip() except EOFError: pass -- 2.53.0 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* Re: [PATCH v2 1/3] usertools/telemetry: add a FOREACH command 2026-05-22 13:37 ` [PATCH v2 1/3] usertools/telemetry: add a FOREACH command Bruce Richardson @ 2026-05-29 0:17 ` fengchengwen 0 siblings, 0 replies; 18+ messages in thread From: fengchengwen @ 2026-05-29 0:17 UTC (permalink / raw) To: Bruce Richardson, dev Acked-by: Chengwen Feng <fengchengwen@huawei.com> On 5/22/2026 9:37 PM, Bruce Richardson wrote: > To simplify querying data from multiple devices, e.g. across ethdevs, or > dmadevs, add a FOREACH command to the python script, allowing you to > run, e.g. /ethdev/list, and then run a second command for each item in > the list, gathering the relevant output values, optionally including an > index counter. > > Simple examples are given in the documentation: > > --> FOREACH /ethdev/list /ethdev/stats .opackets > [0, 0] > > --> FOREACH /ethdev/list /ethdev/stats .ipackets .opackets > [{"ipackets": 0, "opackets": 0}, {"ipackets": 0, "opackets": 0}] > > --> FOREACH i /ethdev/list /ethdev/info,$i .name > [{"i": 0, "name": "0000:16:00.0"}, {"i": 1, "name": "0000:16:00.1"}] > > --> FOREACH i /ethdev/list /ethdev/stats,$i .ipackets .opackets > [{"i": 0, "ipackets": 0, "opackets": 0}, {"i": 1, "ipackets": 0, "opackets": 0}] > > Signed-off-by: Bruce Richardson <bruce.richardson@intel.com> ^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v2 2/3] usertools/telemetry: support using aliases for long commands 2026-05-22 13:37 ` [PATCH v2 0/3] " Bruce Richardson 2026-05-22 13:37 ` [PATCH v2 1/3] usertools/telemetry: add a FOREACH command Bruce Richardson @ 2026-05-22 13:37 ` Bruce Richardson 2026-05-29 0:25 ` fengchengwen 2026-05-22 13:37 ` [PATCH v2 3/3] usertools/telemetry: add help support Bruce Richardson 2 siblings, 1 reply; 18+ messages in thread From: Bruce Richardson @ 2026-05-22 13:37 UTC (permalink / raw) To: dev; +Cc: fengchengwen, Bruce Richardson Similarly to how shell aliases work, allow specifying of short alias commands for dpdk-telemetry.py script. The aliases are read from "$HOME/.dpdk_telemetry_aliases" at startup. Some examples of use from the docs. Alias file contents: # Basic shortcuts ls=/ethdev/list names=FOREACH i /ethdev/list /ethdev/info,$i .name q=quit Alias use: --> ls {"/ethdev/list": [0, 1]} --> names [{"i": 0, "name": "0000: Signed-off-by: Bruce Richardson <bruce.richardson@intel.com> --- doc/guides/howto/telemetry.rst | 34 +++++++++++++++ usertools/dpdk-telemetry.py | 75 ++++++++++++++++++++++++++++++++-- 2 files changed, 105 insertions(+), 4 deletions(-) diff --git a/doc/guides/howto/telemetry.rst b/doc/guides/howto/telemetry.rst index 4bf48c635e..072289aec8 100644 --- a/doc/guides/howto/telemetry.rst +++ b/doc/guides/howto/telemetry.rst @@ -130,6 +130,40 @@ and query information using the telemetry client python script. - With loop variable: returns an array of objects containing the loop variable field and requested value fields. + * Use command aliases. + + The telemetry script can load aliases at startup from:: + + $HOME/.dpdk_telemetry_aliases + + Each alias entry must be in ``alias=command`` format. + Empty lines and lines starting with ``#`` are ignored. + + Example alias file:: + + # Basic shortcuts + ls=/ethdev/list + names=FOREACH i /ethdev/list /ethdev/info,$i .name + q=quit + + Alias behavior is intentionally similar to shell aliases: + + - The first token of the entered input is checked for an alias match. + - If matched, that first token is replaced with its expansion. + - Alias expansion is recursive (aliases can expand to other aliases). + - Expansion has a safety limit to prevent infinite loops. + + Examples:: + + --> ls + {"/ethdev/list": [0, 1]} + + --> names + [{"i": 0, "name": "0000:16:00.0"}, {"i": 1, "name": "0000:16:00.1"}] + + --> q + # exits the client + Connecting to Different DPDK Processes -------------------------------------- diff --git a/usertools/dpdk-telemetry.py b/usertools/dpdk-telemetry.py index 2de10cff69..8b976160e0 100755 --- a/usertools/dpdk-telemetry.py +++ b/usertools/dpdk-telemetry.py @@ -21,6 +21,70 @@ SOCKET_NAME = "dpdk_telemetry.{}".format(TELEMETRY_VERSION) DEFAULT_PREFIX = "rte" CMDS = [] +ALIASES = {} +ALIAS_FILE = ".dpdk_telemetry_aliases" +MAX_ALIAS_EXPANSIONS = 32 + + +def load_aliases(): + """Load aliases from $HOME/.dpdk_telemetry_aliases""" + aliases = {} + home = os.environ.get("HOME") + if not home: + return aliases + + alias_path = os.path.join(home, ALIAS_FILE) + if not os.path.isfile(alias_path): + return aliases + + try: + with open(alias_path) as alias_file: + for line_num, line in enumerate(alias_file, start=1): + entry = line.strip() + if not entry or entry.startswith("#"): + continue + if "=" not in entry: + print( + "Warning: ignoring malformed alias at {}:{}".format(alias_path, line_num), + file=sys.stderr, + ) + continue + name, command = entry.split("=", 1) + name = name.strip() + command = command.strip() + if not name or not command: + print( + "Warning: ignoring malformed alias at {}:{}".format(alias_path, line_num), + file=sys.stderr, + ) + continue + aliases[name] = command + except OSError as e: + print("Warning: failed to read {}: {}".format(alias_path, e), file=sys.stderr) + + return aliases + + +def expand_aliases(text, aliases): + """Expand aliases similarly to shell aliases on the first token""" + expanded = text + for _ in range(MAX_ALIAS_EXPANSIONS): + stripped = expanded.lstrip() + if not stripped: + return expanded + + parts = stripped.split(None, 1) + first = parts[0] + rest = parts[1] if len(parts) > 1 else "" + + if first not in aliases: + return expanded + + alias_value = aliases[first] + expanded = "{} {}".format(alias_value, rest).strip() if rest else alias_value + + print("Warning: alias expansion limit reached", file=sys.stderr) + return expanded def send_command(sock, cmd, output_buf_len, echo=False, pretty=False): @@ -262,10 +326,12 @@ def handle_socket(args, path): # interactive prompt try: - text = input(prompt).strip() - while text != "quit": - handle_command(sock, output_buf_len, text, pretty=prompt) + while True: text = input(prompt).strip() + expanded = expand_aliases(text, ALIASES) + if expanded == "quit": + break + handle_command(sock, output_buf_len, expanded, pretty=prompt) except EOFError: pass finally: @@ -274,7 +340,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"] + CMDS + all_cmds = ["quit"] + list(ALIASES.keys()) + CMDS if text: matches = [c for c in all_cmds if c.startswith(text)] else: @@ -304,6 +370,7 @@ def readline_complete(text, state): help="List all possible file-prefixes and exit", ) args = parser.parse_args() +ALIASES = load_aliases() if args.list: list_fp() sys.exit(0) -- 2.53.0 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* Re: [PATCH v2 2/3] usertools/telemetry: support using aliases for long commands 2026-05-22 13:37 ` [PATCH v2 2/3] usertools/telemetry: support using aliases for long commands Bruce Richardson @ 2026-05-29 0:25 ` fengchengwen 0 siblings, 0 replies; 18+ messages in thread From: fengchengwen @ 2026-05-29 0:25 UTC (permalink / raw) To: Bruce Richardson, dev On 5/22/2026 9:37 PM, Bruce Richardson wrote: > Similarly to how shell aliases work, allow specifying of short alias > commands for dpdk-telemetry.py script. The aliases are read from > "$HOME/.dpdk_telemetry_aliases" at startup. > > Some examples of use from the docs. Alias file contents: > > # Basic shortcuts > ls=/ethdev/list > names=FOREACH i /ethdev/list /ethdev/info,$i .name > q=quit > > Alias use: > > --> ls > {"/ethdev/list": [0, 1]} > > --> names > [{"i": 0, "name": "0000: > > Signed-off-by: Bruce Richardson <bruce.richardson@intel.com> > --- > doc/guides/howto/telemetry.rst | 34 +++++++++++++++ > usertools/dpdk-telemetry.py | 75 ++++++++++++++++++++++++++++++++-- > 2 files changed, 105 insertions(+), 4 deletions(-) > > diff --git a/doc/guides/howto/telemetry.rst b/doc/guides/howto/telemetry.rst > index 4bf48c635e..072289aec8 100644 > --- a/doc/guides/howto/telemetry.rst > +++ b/doc/guides/howto/telemetry.rst > @@ -130,6 +130,40 @@ and query information using the telemetry client python script. > - With loop variable: returns an array of objects containing the loop > variable field and requested value fields. > > + * Use command aliases. > + > + The telemetry script can load aliases at startup from:: > + > + $HOME/.dpdk_telemetry_aliases How about add one parameter to specific aliases file location > + > + Each alias entry must be in ``alias=command`` format. > + Empty lines and lines starting with ``#`` are ignored. > + > + Example alias file:: > + > + # Basic shortcuts > + ls=/ethdev/list > + names=FOREACH i /ethdev/list /ethdev/info,$i .name > + q=quit > + > + Alias behavior is intentionally similar to shell aliases: > + > + - The first token of the entered input is checked for an alias match. > + - If matched, that first token is replaced with its expansion. > + - Alias expansion is recursive (aliases can expand to other aliases). > + - Expansion has a safety limit to prevent infinite loops. > + > + Examples:: > + > + --> ls > + {"/ethdev/list": [0, 1]} > + > + --> names > + [{"i": 0, "name": "0000:16:00.0"}, {"i": 1, "name": "0000:16:00.1"}] > + > + --> q > + # exits the client > + > > Connecting to Different DPDK Processes > -------------------------------------- > diff --git a/usertools/dpdk-telemetry.py b/usertools/dpdk-telemetry.py > index 2de10cff69..8b976160e0 100755 > --- a/usertools/dpdk-telemetry.py > +++ b/usertools/dpdk-telemetry.py > @@ -21,6 +21,70 @@ > SOCKET_NAME = "dpdk_telemetry.{}".format(TELEMETRY_VERSION) > DEFAULT_PREFIX = "rte" > CMDS = [] > +ALIASES = {} > +ALIAS_FILE = ".dpdk_telemetry_aliases" > +MAX_ALIAS_EXPANSIONS = 32 > + > + > +def load_aliases(): > + """Load aliases from $HOME/.dpdk_telemetry_aliases""" > + aliases = {} > + home = os.environ.get("HOME") > + if not home: > + return aliases > + > + alias_path = os.path.join(home, ALIAS_FILE) > + if not os.path.isfile(alias_path): > + return aliases > + > + try: > + with open(alias_path) as alias_file: > + for line_num, line in enumerate(alias_file, start=1): > + entry = line.strip() > + if not entry or entry.startswith("#"): > + continue > + if "=" not in entry: > + print( > + "Warning: ignoring malformed alias at {}:{}".format(alias_path, line_num), > + file=sys.stderr, > + ) > + continue > + name, command = entry.split("=", 1) > + name = name.strip() > + command = command.strip() > + if not name or not command: > + print( > + "Warning: ignoring malformed alias at {}:{}".format(alias_path, line_num), > + file=sys.stderr, > + ) > + continue > + aliases[name] = command > + except OSError as e: > + print("Warning: failed to read {}: {}".format(alias_path, e), file=sys.stderr) If load OK, please add one promote at begin: Loaded xxx command aliases, see `help alias` for detail > + > + return aliases > + > + ^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v2 3/3] usertools/telemetry: add help support 2026-05-22 13:37 ` [PATCH v2 0/3] " Bruce Richardson 2026-05-22 13:37 ` [PATCH v2 1/3] usertools/telemetry: add a FOREACH command Bruce Richardson 2026-05-22 13:37 ` [PATCH v2 2/3] usertools/telemetry: support using aliases for long commands Bruce Richardson @ 2026-05-22 13:37 ` Bruce Richardson 2026-05-29 0:27 ` fengchengwen 2 siblings, 1 reply; 18+ messages in thread From: Bruce Richardson @ 2026-05-22 13:37 UTC (permalink / raw) To: dev; +Cc: fengchengwen, Bruce Richardson 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 <bruce.richardson@intel.com> --- usertools/dpdk-telemetry.py | 61 ++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/usertools/dpdk-telemetry.py b/usertools/dpdk-telemetry.py index 8b976160e0..2c88b91517 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(): """Load aliases from $HOME/.dpdk_telemetry_aliases""" @@ -203,10 +223,49 @@ 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 + + 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) @@ -340,7 +399,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 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* Re: [PATCH v2 3/3] usertools/telemetry: add help support 2026-05-22 13:37 ` [PATCH v2 3/3] usertools/telemetry: add help support Bruce Richardson @ 2026-05-29 0:27 ` fengchengwen 0 siblings, 0 replies; 18+ messages in thread From: fengchengwen @ 2026-05-29 0:27 UTC (permalink / raw) To: Bruce Richardson, dev On 5/22/2026 9:37 PM, Bruce Richardson wrote: > 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 <bruce.richardson@intel.com> > --- > usertools/dpdk-telemetry.py | 61 ++++++++++++++++++++++++++++++++++++- > 1 file changed, 60 insertions(+), 1 deletion(-) > > diff --git a/usertools/dpdk-telemetry.py b/usertools/dpdk-telemetry.py > index 8b976160e0..2c88b91517 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 Please add help alias to show ALL loaded command alias ^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v3 0/3] extend interactive telemetry script 2026-05-21 15:39 [PATCH 0/2] extend interactive telemetry script Bruce Richardson ` (3 preceding siblings ...) 2026-05-22 13:37 ` [PATCH v2 0/3] " Bruce Richardson @ 2026-06-09 16:13 ` Bruce Richardson 2026-06-09 16:13 ` [PATCH v3 1/3] usertools/telemetry: add a FOREACH command Bruce Richardson ` (3 more replies) 4 siblings, 4 replies; 18+ messages in thread From: Bruce Richardson @ 2026-06-09 16:13 UTC (permalink / raw) To: dev; +Cc: fengchengwen, Bruce Richardson To simplify interactive telemetry script for general use, i.e. not from other scripts, we can add two new features to it: 1. Support for FOREACH to allow gathering a set of output values across a list of ports or devices, e.g. ethdevs or rawdevs. 2. Support having predefined aliases in a file in the user's home directory to simplify the use of more complicated FOREACH commands. Putting these together, we can create new commands such as "eth_names". bruce@host:$ cat ~/.dpdk_telemetry_aliases eth_names=FOREACH index /ethdev/list /ethdev/info,$index .name bruce@host:$ echo eth_names | ./usertools/dpdk-telemetry.py | jq [ { "index": 0, "name": "0000:16:00.0" }, { "index": 1, "name": "0000:16:00.1" } ] --- v3: updated based on review feedback from Chengwen: - added arg to override alias file - printed one-line summary of alias count loaded - improved doc for "help" command - added "help alias" to list aliases. v2: added third patch with "help" command giving more details on how to use the various commands. Bruce Richardson (3): usertools/telemetry: add a FOREACH command usertools/telemetry: support using aliases for long commands usertools/telemetry: add help support doc/guides/howto/telemetry.rst | 106 ++++++++++++- usertools/dpdk-telemetry.py | 278 ++++++++++++++++++++++++++++++++- 2 files changed, 373 insertions(+), 11 deletions(-) -- 2.53.0 ^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v3 1/3] usertools/telemetry: add a FOREACH command 2026-06-09 16:13 ` [PATCH v3 0/3] extend interactive telemetry script Bruce Richardson @ 2026-06-09 16:13 ` Bruce Richardson 2026-06-09 16:13 ` [PATCH v3 2/3] usertools/telemetry: support using aliases for long commands Bruce Richardson ` (2 subsequent siblings) 3 siblings, 0 replies; 18+ messages in thread From: Bruce Richardson @ 2026-06-09 16:13 UTC (permalink / raw) To: dev; +Cc: fengchengwen, Bruce Richardson To simplify querying data from multiple devices, e.g. across ethdevs, or dmadevs, add a FOREACH command to the python script, allowing you to run, e.g. /ethdev/list, and then run a second command for each item in the list, gathering the relevant output values, optionally including an index counter. Simple examples are given in the documentation: --> FOREACH /ethdev/list /ethdev/stats .opackets [0, 0] --> FOREACH /ethdev/list /ethdev/stats .ipackets .opackets [{"ipackets": 0, "opackets": 0}, {"ipackets": 0, "opackets": 0}] --> FOREACH i /ethdev/list /ethdev/info,$i .name [{"i": 0, "name": "0000:16:00.0"}, {"i": 1, "name": "0000:16:00.1"}] --> FOREACH i /ethdev/list /ethdev/stats,$i .ipackets .opackets [{"i": 0, "ipackets": 0, "opackets": 0}, {"i": 1, "ipackets": 0, "opackets": 0}] Signed-off-by: Bruce Richardson <bruce.richardson@intel.com> Acked-by: Chengwen Feng <fengchengwen@huawei.com> --- doc/guides/howto/telemetry.rst | 42 +++++++++++ usertools/dpdk-telemetry.py | 128 ++++++++++++++++++++++++++++++++- 2 files changed, 167 insertions(+), 3 deletions(-) diff --git a/doc/guides/howto/telemetry.rst b/doc/guides/howto/telemetry.rst index 0464c431fe..4bf48c635e 100644 --- a/doc/guides/howto/telemetry.rst +++ b/doc/guides/howto/telemetry.rst @@ -88,6 +88,48 @@ and query information using the telemetry client python script. {"/help": {"/ethdev/xstats": "Returns the extended stats for a port. Parameters: int port_id"}} + * Run a compound query using ``FOREACH``. + + The ``FOREACH`` command runs a list command, iterates each returned item, + runs a second command for each item, and emits combined JSON output. + + Start with the simplest form (no loop variable):: + + FOREACH /<list_cmd> /<iter_cmd> .<field> [.<field> ...] + + To include numbered output, use a loop variable:: + + FOREACH <var> /<list_cmd> /<iter_cmd_with_$var> .<field> [.<field> ...] + + Notes: + + - Field selectors are whitespace-separated tokens, each starting with ``.``. + - In no-variable mode, the iter command is called as ``/<iter_cmd>,<item>``. + - In loop-variable mode, use ``$<var>`` in the iter command where the + item value should be substituted. + + Examples:: + + --> FOREACH /ethdev/list /ethdev/stats .opackets + [0, 0] + + --> FOREACH /ethdev/list /ethdev/stats .ipackets .opackets + [{"ipackets": 0, "opackets": 0}, {"ipackets": 0, "opackets": 0}] + + --> FOREACH i /ethdev/list /ethdev/info,$i .name + [{"i": 0, "name": "0000:16:00.0"}, {"i": 1, "name": "0000:16:00.1"}] + + --> FOREACH i /ethdev/list /ethdev/stats,$i .ipackets .opackets + [{"i": 0, "ipackets": 0, "opackets": 0}, {"i": 1, "ipackets": 0, "opackets": 0}] + + Output behavior: + + - Without loop variable and one field: returns an array of values. + - Without loop variable and multiple fields: returns an array of objects + containing named value fields. + - With loop variable: returns an array of objects containing the loop + variable field and requested value fields. + Connecting to Different DPDK Processes -------------------------------------- diff --git a/usertools/dpdk-telemetry.py b/usertools/dpdk-telemetry.py index 09258a1f7e..2de10cff69 100755 --- a/usertools/dpdk-telemetry.py +++ b/usertools/dpdk-telemetry.py @@ -23,6 +23,130 @@ CMDS = [] +def send_command(sock, cmd, output_buf_len, echo=False, pretty=False): + """Send a telemetry command and return the parsed JSON reply""" + sock.send(cmd.encode()) + return read_socket(sock, output_buf_len, echo, pretty) + + +def get_cmd_payload(reply, cmd): + """Return the payload for a command response if present""" + if isinstance(reply, dict) and len(reply) == 1: + return next(iter(reply.values())) + return None + + +def get_path_value(payload, path): + """Resolve a dotted path (e.g. '.name' or '.a.b') from a JSON payload""" + if not path: + return payload + + keys = [k for k in path.lstrip(".").split(".") if k] + val = payload + for key in keys: + if not isinstance(val, dict) or key not in val: + return None + val = val[key] + return val + + +def parse_selectors(selector_text): + """Parse whitespace-separated dotted selectors""" + selectors = selector_text.split() + if not selectors: + print("Invalid FOREACH syntax: missing selector") + return None + if any(not selector.startswith(".") for selector in selectors): + print("Invalid FOREACH syntax: selector must start with '.'") + return None + return selectors + + +def parse_foreach(text): + """Parse FOREACH [<var>] /<cmd> /<parameterized cmd> .<value> [.<value> ...]""" + try: + tokens = text.split(None, 3) + except ValueError: + print("Invalid FOREACH syntax") + return None + + if len(tokens) != 4: + print("Invalid FOREACH syntax") + return None + + _, arg1, arg2, arg3 = tokens + if arg1.startswith("/"): + var_name = None + list_cmd = arg1 + iter_cmd = arg2 + selector_text = arg3 + else: + var_name = arg1 + list_cmd = arg2 + try: + iter_cmd, selector_text = arg3.split(None, 1) + except ValueError: + print("Invalid FOREACH syntax") + return None + + if not list_cmd.startswith("/") or not iter_cmd.startswith("/"): + print("Invalid FOREACH syntax: commands must start with '/'") + return None + + selectors = parse_selectors(selector_text) + if selectors is None: + return None + + return var_name, list_cmd, iter_cmd, selectors + + +def build_foreach_result(item, var_name, payload, selectors): + """Build one FOREACH result entry based on selector count and index mode""" + values = {selector.lstrip("."): get_path_value(payload, selector) for selector in selectors} + + if var_name is None and len(selectors) == 1: + return next(iter(values.values())) + if var_name is None: + return values + + return {var_name: item, **values} + + +def handle_foreach(sock, output_buf_len, text, pretty=False): + """Handle FOREACH queries and print telemetry-like JSON array output""" + parsed = parse_foreach(text) + if parsed is None: + return + var_name, list_cmd, iter_cmd, selectors = parsed + + list_reply = send_command(sock, list_cmd, output_buf_len) + values = get_cmd_payload(list_reply, list_cmd) + if not isinstance(values, list): + print("FOREACH source command did not return a JSON array") + return + + output = [] + for item in values: + if var_name is None: + cmd = "{},{}".format(iter_cmd, item) + else: + cmd = iter_cmd.replace("$" + var_name, str(item)) + item_reply = send_command(sock, cmd, output_buf_len) + item_payload = get_cmd_payload(item_reply, cmd) + output.append(build_foreach_result(item, var_name, item_payload, selectors)) + + indent = 2 if pretty else None + print(json.dumps(output, indent=indent)) + + +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.startswith("FOREACH "): + handle_foreach(sock, output_buf_len, text, pretty) + + def read_socket(sock, buf_len, echo=True, pretty=False): """Read data from socket and return it in JSON format""" reply = sock.recv(buf_len).decode() @@ -140,9 +264,7 @@ def handle_socket(args, path): try: text = input(prompt).strip() while text != "quit": - if text.startswith("/"): - sock.send(text.encode()) - read_socket(sock, output_buf_len, pretty=prompt) + handle_command(sock, output_buf_len, text, pretty=prompt) text = input(prompt).strip() except EOFError: pass -- 2.53.0 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v3 2/3] usertools/telemetry: support using aliases for long commands 2026-06-09 16:13 ` [PATCH v3 0/3] extend interactive telemetry script Bruce Richardson 2026-06-09 16:13 ` [PATCH v3 1/3] usertools/telemetry: add a FOREACH command Bruce Richardson @ 2026-06-09 16:13 ` Bruce Richardson 2026-06-09 16:14 ` [PATCH v3 3/3] usertools/telemetry: add help support Bruce Richardson 2026-06-10 1:39 ` [PATCH v3 0/3] extend interactive telemetry script fengchengwen 3 siblings, 0 replies; 18+ messages in thread From: Bruce Richardson @ 2026-06-09 16:13 UTC (permalink / raw) To: dev; +Cc: fengchengwen, Bruce Richardson Similarly to how shell aliases work, allow specifying of short alias commands for dpdk-telemetry.py script. The aliases are read from "$HOME/.dpdk_telemetry_aliases" at startup. Some examples of use from the docs. Alias file contents: # Basic shortcuts ls=/ethdev/list names=FOREACH i /ethdev/list /ethdev/info,$i .name q=quit Alias use: --> ls {"/ethdev/list": [0, 1]} --> names [{"i": 0, "name": "0000: Signed-off-by: Bruce Richardson <bruce.richardson@intel.com> --- v2: added support for providing an alias path on cmdline. added summary printout of how many aliases were loaded. --- doc/guides/howto/telemetry.rst | 35 ++++++++++++++ usertools/dpdk-telemetry.py | 85 ++++++++++++++++++++++++++++++++-- 2 files changed, 116 insertions(+), 4 deletions(-) diff --git a/doc/guides/howto/telemetry.rst b/doc/guides/howto/telemetry.rst index 4bf48c635e..bdefbdc6a6 100644 --- a/doc/guides/howto/telemetry.rst +++ b/doc/guides/howto/telemetry.rst @@ -130,6 +130,41 @@ and query information using the telemetry client python script. - With loop variable: returns an array of objects containing the loop variable field and requested value fields. + * Use command aliases. + + The telemetry script can load aliases at startup from:: + + $HOME/.dpdk_telemetry_aliases + + or from a custom path provided via the ``--alias-file`` script flag. + Each alias entry must be in ``alias=command`` format. + Empty lines and lines starting with ``#`` are ignored. + + Example alias file:: + + # Basic shortcuts + ls=/ethdev/list + names=FOREACH i /ethdev/list /ethdev/info,$i .name + q=quit + + Alias behavior is intentionally similar to shell aliases: + + - The first token of the entered input is checked for an alias match. + - If matched, that first token is replaced with its expansion. + - Alias expansion is recursive (aliases can expand to other aliases). + - Expansion has a safety limit to prevent infinite loops. + + Examples:: + + --> ls + {"/ethdev/list": [0, 1]} + + --> names + [{"i": 0, "name": "0000:16:00.0"}, {"i": 1, "name": "0000:16:00.1"}] + + --> q + # exits the client + Connecting to Different DPDK Processes -------------------------------------- diff --git a/usertools/dpdk-telemetry.py b/usertools/dpdk-telemetry.py index 2de10cff69..7a25b78730 100755 --- a/usertools/dpdk-telemetry.py +++ b/usertools/dpdk-telemetry.py @@ -21,6 +21,76 @@ SOCKET_NAME = "dpdk_telemetry.{}".format(TELEMETRY_VERSION) DEFAULT_PREFIX = "rte" CMDS = [] +ALIASES = {} +ALIAS_FILE = ".dpdk_telemetry_aliases" +MAX_ALIAS_EXPANSIONS = 32 + + +def load_aliases(alias_path=None): + """Load aliases from $HOME/.dpdk_telemetry_aliases or a custom path if provided""" + aliases = {} + if alias_path and not os.path.isfile(alias_path): + print("Warning: alias file {} not found, skipping".format(alias_path), file=sys.stderr) + return aliases + + if not alias_path: + home = os.environ.get("HOME") + if not home: + return aliases + + alias_path = os.path.join(home, ALIAS_FILE) + if not os.path.isfile(alias_path): + return aliases + + try: + with open(alias_path) as alias_file: + for line_num, line in enumerate(alias_file, start=1): + entry = line.strip() + if not entry or entry.startswith("#"): + continue + if "=" not in entry: + print( + "Warning: ignoring malformed alias at {}:{}".format(alias_path, line_num), + file=sys.stderr, + ) + continue + name, command = entry.split("=", 1) + name = name.strip() + command = command.strip() + if not name or not command: + print( + "Warning: ignoring malformed alias at {}:{}".format(alias_path, line_num), + file=sys.stderr, + ) + continue + aliases[name] = command + except OSError as e: + print("Warning: failed to read {}: {}".format(alias_path, e), file=sys.stderr) + + print("Loaded {} aliases from {}".format(len(aliases), alias_path)) + return aliases + + +def expand_aliases(text, aliases): + """Expand aliases similarly to shell aliases on the first token""" + expanded = text + for _ in range(MAX_ALIAS_EXPANSIONS): + stripped = expanded.lstrip() + if not stripped: + return expanded + + parts = stripped.split(None, 1) + first = parts[0] + rest = parts[1] if len(parts) > 1 else "" + + if first not in aliases: + return expanded + + alias_value = aliases[first] + expanded = "{} {}".format(alias_value, rest).strip() if rest else alias_value + + print("Warning: alias expansion limit reached", file=sys.stderr) + return expanded def send_command(sock, cmd, output_buf_len, echo=False, pretty=False): @@ -262,10 +332,12 @@ def handle_socket(args, path): # interactive prompt try: - text = input(prompt).strip() - while text != "quit": - handle_command(sock, output_buf_len, text, pretty=prompt) + while True: text = input(prompt).strip() + expanded = expand_aliases(text, ALIASES) + if expanded == "quit": + break + handle_command(sock, output_buf_len, expanded, pretty=prompt) except EOFError: pass finally: @@ -274,7 +346,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"] + CMDS + all_cmds = ["quit"] + list(ALIASES.keys()) + CMDS if text: matches = [c for c in all_cmds if c.startswith(text)] else: @@ -293,6 +365,10 @@ def readline_complete(text, state): default=DEFAULT_PREFIX, help="Provide file-prefix for DPDK runtime directory", ) +parser.add_argument( + "--alias-file", + help=f"Provide a custom alias file instead of $HOME/{ALIAS_FILE}", +) parser.add_argument( "-i", "--instance", default="0", type=int, help="Provide instance number for DPDK application" ) @@ -304,6 +380,7 @@ def readline_complete(text, state): help="List all possible file-prefixes and exit", ) args = parser.parse_args() +ALIASES = load_aliases(args.alias_file if args.alias_file else None) if args.list: list_fp() sys.exit(0) -- 2.53.0 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v3 3/3] usertools/telemetry: add help support 2026-06-09 16:13 ` [PATCH v3 0/3] extend interactive telemetry script Bruce Richardson 2026-06-09 16:13 ` [PATCH v3 1/3] usertools/telemetry: add a FOREACH command Bruce Richardson 2026-06-09 16:13 ` [PATCH v3 2/3] usertools/telemetry: support using aliases for long commands Bruce Richardson @ 2026-06-09 16:14 ` Bruce Richardson 2026-06-09 16:17 ` Bruce Richardson 2026-06-10 1:39 ` [PATCH v3 0/3] extend interactive telemetry script fengchengwen 3 siblings, 1 reply; 18+ messages in thread From: Bruce Richardson @ 2026-06-09 16:14 UTC (permalink / raw) To: dev; +Cc: fengchengwen, Bruce Richardson 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 <bruce.richardson@intel.com> --- 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 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* Re: [PATCH v3 3/3] usertools/telemetry: add help support 2026-06-09 16:14 ` [PATCH v3 3/3] usertools/telemetry: add help support Bruce Richardson @ 2026-06-09 16:17 ` Bruce Richardson 0 siblings, 0 replies; 18+ messages in thread From: Bruce Richardson @ 2026-06-09 16:17 UTC (permalink / raw) To: dev; +Cc: fengchengwen On Tue, Jun 09, 2026 at 05:14:00PM +0100, Bruce Richardson wrote: > 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 <bruce.richardson@intel.com> > --- > 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, Checkpatch in CI correctly points out the typo for "endpoints" here. Please fix on apply if no v4 is otherwise necessary. thanks, /Bruce ^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v3 0/3] extend interactive telemetry script 2026-06-09 16:13 ` [PATCH v3 0/3] extend interactive telemetry script Bruce Richardson ` (2 preceding siblings ...) 2026-06-09 16:14 ` [PATCH v3 3/3] usertools/telemetry: add help support Bruce Richardson @ 2026-06-10 1:39 ` fengchengwen 3 siblings, 0 replies; 18+ messages in thread From: fengchengwen @ 2026-06-10 1:39 UTC (permalink / raw) To: Bruce Richardson, dev Series-acked-by: Chengwen Feng <fengchengwen@huawei.com> On 6/10/2026 12:13 AM, Bruce Richardson wrote: > To simplify interactive telemetry script for general use, i.e. not from > other scripts, we can add two new features to it: > > 1. Support for FOREACH to allow gathering a set of output values across > a list of ports or devices, e.g. ethdevs or rawdevs. > 2. Support having predefined aliases in a file in the user's home > directory to simplify the use of more complicated FOREACH commands. > > Putting these together, we can create new commands such as "eth_names". > > bruce@host:$ cat ~/.dpdk_telemetry_aliases > eth_names=FOREACH index /ethdev/list /ethdev/info,$index .name > > bruce@host:$ echo eth_names | ./usertools/dpdk-telemetry.py | jq > [ > { > "index": 0, > "name": "0000:16:00.0" > }, > { > "index": 1, > "name": "0000:16:00.1" > } > ] > > --- > v3: updated based on review feedback from Chengwen: > - added arg to override alias file > - printed one-line summary of alias count loaded > - improved doc for "help" command > - added "help alias" to list aliases. > v2: added third patch with "help" command giving more details on > how to use the various commands. > > Bruce Richardson (3): > usertools/telemetry: add a FOREACH command > usertools/telemetry: support using aliases for long commands > usertools/telemetry: add help support > > doc/guides/howto/telemetry.rst | 106 ++++++++++++- > usertools/dpdk-telemetry.py | 278 ++++++++++++++++++++++++++++++++- > 2 files changed, 373 insertions(+), 11 deletions(-) > > -- > 2.53.0 > > ^ permalink raw reply [flat|nested] 18+ messages in thread
end of thread, other threads:[~2026-06-10 1:42 UTC | newest] Thread overview: 18+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-05-21 15:39 [PATCH 0/2] extend interactive telemetry script Bruce Richardson 2026-05-21 15:39 ` [PATCH 1/2] usertools/telemetry: add a FOREACH command Bruce Richardson 2026-05-21 15:39 ` [PATCH 2/2] usertools/telemetry: support using aliases for long commands Bruce Richardson 2026-05-22 0:44 ` [PATCH 0/2] extend interactive telemetry script fengchengwen 2026-05-22 7:51 ` Bruce Richardson 2026-05-22 13:37 ` [PATCH v2 0/3] " Bruce Richardson 2026-05-22 13:37 ` [PATCH v2 1/3] usertools/telemetry: add a FOREACH command Bruce Richardson 2026-05-29 0:17 ` fengchengwen 2026-05-22 13:37 ` [PATCH v2 2/3] usertools/telemetry: support using aliases for long commands Bruce Richardson 2026-05-29 0:25 ` fengchengwen 2026-05-22 13:37 ` [PATCH v2 3/3] usertools/telemetry: add help support Bruce Richardson 2026-05-29 0:27 ` fengchengwen 2026-06-09 16:13 ` [PATCH v3 0/3] extend interactive telemetry script Bruce Richardson 2026-06-09 16:13 ` [PATCH v3 1/3] usertools/telemetry: add a FOREACH command Bruce Richardson 2026-06-09 16:13 ` [PATCH v3 2/3] usertools/telemetry: support using aliases for long commands Bruce Richardson 2026-06-09 16:14 ` [PATCH v3 3/3] usertools/telemetry: add help support Bruce Richardson 2026-06-09 16:17 ` Bruce Richardson 2026-06-10 1:39 ` [PATCH v3 0/3] extend interactive telemetry script fengchengwen
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox