xref: /dpdk/usertools/dpdk-telemetry.py (revision 66542840dfd184c06813e6740664b280ef77d4e0)
13f6f8362SLouise Kilheeney#! /usr/bin/env python3
26a2967c1SBruce Richardson# SPDX-License-Identifier: BSD-3-Clause
36a2967c1SBruce Richardson# Copyright(c) 2020 Intel Corporation
46a2967c1SBruce Richardson
56a2967c1SBruce Richardson"""
66a2967c1SBruce RichardsonScript to be used with V2 Telemetry.
76a2967c1SBruce RichardsonAllows the user input commands and read the Telemetry response.
86a2967c1SBruce Richardson"""
96a2967c1SBruce Richardson
106a2967c1SBruce Richardsonimport socket
116a2967c1SBruce Richardsonimport os
1272727e0dSBruce Richardsonimport sys
13b95af194SBruce Richardsonimport glob
146a2967c1SBruce Richardsonimport json
15d786e30eSBruce Richardsonimport errno
166a2967c1SBruce Richardsonimport readline
172d9a697eSKevin Laatzimport argparse
186a2967c1SBruce Richardson
196a2967c1SBruce Richardson# global vars
206a2967c1SBruce RichardsonTELEMETRY_VERSION = "v2"
21b95af194SBruce RichardsonSOCKET_NAME = 'dpdk_telemetry.{}'.format(TELEMETRY_VERSION)
22b95af194SBruce RichardsonDEFAULT_PREFIX = 'rte'
236a2967c1SBruce RichardsonCMDS = []
246a2967c1SBruce Richardson
256a2967c1SBruce Richardson
26*66542840SChengwen Fengdef read_socket(sock, buf_len, echo=True, pretty=False):
276a2967c1SBruce Richardson    """ Read data from socket and return it in JSON format """
286a2967c1SBruce Richardson    reply = sock.recv(buf_len).decode()
296a2967c1SBruce Richardson    try:
306a2967c1SBruce Richardson        ret = json.loads(reply)
316a2967c1SBruce Richardson    except json.JSONDecodeError:
326a2967c1SBruce Richardson        print("Error in reply: ", reply)
336a2967c1SBruce Richardson        sock.close()
346a2967c1SBruce Richardson        raise
356a2967c1SBruce Richardson    if echo:
36*66542840SChengwen Feng        indent = 2 if pretty else None
37*66542840SChengwen Feng        print(json.dumps(ret, indent=indent))
386a2967c1SBruce Richardson    return ret
396a2967c1SBruce Richardson
406a2967c1SBruce Richardson
41d786e30eSBruce Richardsondef get_app_name(pid):
42d786e30eSBruce Richardson    """ return the app name for a given PID, for printing """
43d786e30eSBruce Richardson    proc_cmdline = os.path.join('/proc', str(pid), 'cmdline')
44d786e30eSBruce Richardson    try:
45d786e30eSBruce Richardson        with open(proc_cmdline) as f:
46d786e30eSBruce Richardson            argv0 = f.read(1024).split('\0')[0]
47d786e30eSBruce Richardson            return os.path.basename(argv0)
48d786e30eSBruce Richardson    except IOError as e:
49d786e30eSBruce Richardson        # ignore file not found errors
50d786e30eSBruce Richardson        if e.errno != errno.ENOENT:
51d786e30eSBruce Richardson            raise
52d786e30eSBruce Richardson    return None
53d786e30eSBruce Richardson
54d786e30eSBruce Richardson
55b95af194SBruce Richardsondef find_sockets(path):
56b95af194SBruce Richardson    """ Find any possible sockets to connect to and return them """
57b95af194SBruce Richardson    return glob.glob(os.path.join(path, SOCKET_NAME + '*'))
58b95af194SBruce Richardson
59b95af194SBruce Richardson
60b95af194SBruce Richardsondef print_socket_options(prefix, paths):
61b95af194SBruce Richardson    """ Given a set of socket paths, give the commands needed to connect """
62b95af194SBruce Richardson    cmd = sys.argv[0]
63b95af194SBruce Richardson    if prefix != DEFAULT_PREFIX:
64b95af194SBruce Richardson        cmd += " -f " + prefix
65b95af194SBruce Richardson    for s in sorted(paths):
66b95af194SBruce Richardson        sock_name = os.path.basename(s)
67b95af194SBruce Richardson        if sock_name.endswith(TELEMETRY_VERSION):
68b95af194SBruce Richardson            print("- {}  # Connect with '{}'".format(os.path.basename(s),
69b95af194SBruce Richardson                                                     cmd))
70b95af194SBruce Richardson        else:
71b95af194SBruce Richardson            print("- {}  # Connect with '{} -i {}'".format(os.path.basename(s),
72b95af194SBruce Richardson                                                           cmd,
73b95af194SBruce Richardson                                                           s.split(':')[-1]))
74b95af194SBruce Richardson
75b95af194SBruce Richardson
769055bcdeSConor Walshdef get_dpdk_runtime_dir(fp):
779055bcdeSConor Walsh    """ Using the same logic as in DPDK's EAL, get the DPDK runtime directory
789055bcdeSConor Walsh    based on the file-prefix and user """
791835a22fSStephen Hemminger    run_dir = os.environ.get('RUNTIME_DIRECTORY')
801835a22fSStephen Hemminger    if not run_dir:
819055bcdeSConor Walsh        if (os.getuid() == 0):
821835a22fSStephen Hemminger            run_dir = '/var/run'
831835a22fSStephen Hemminger        else:
841835a22fSStephen Hemminger            run_dir = os.environ.get('XDG_RUNTIME_DIR', '/tmp')
851835a22fSStephen Hemminger    return os.path.join(run_dir, 'dpdk', fp)
869055bcdeSConor Walsh
879055bcdeSConor Walsh
889055bcdeSConor Walshdef list_fp():
899055bcdeSConor Walsh    """ List all available file-prefixes to user """
909055bcdeSConor Walsh    path = get_dpdk_runtime_dir('')
919055bcdeSConor Walsh    sockets = glob.glob(os.path.join(path, "*", SOCKET_NAME + "*"))
929055bcdeSConor Walsh    prefixes = []
939055bcdeSConor Walsh    if not sockets:
949055bcdeSConor Walsh        print("No DPDK apps with telemetry enabled available")
959055bcdeSConor Walsh    else:
969055bcdeSConor Walsh        print("Valid file-prefixes:\n")
979055bcdeSConor Walsh    for s in sockets:
989055bcdeSConor Walsh        prefixes.append(os.path.relpath(os.path.dirname(s), start=path))
999055bcdeSConor Walsh    for p in sorted(set(prefixes)):
1009055bcdeSConor Walsh        print(p)
1019055bcdeSConor Walsh        print_socket_options(p, glob.glob(os.path.join(path, p,
1029055bcdeSConor Walsh                                                       SOCKET_NAME + "*")))
1039055bcdeSConor Walsh
1049055bcdeSConor Walsh
105b95af194SBruce Richardsondef handle_socket(args, path):
1066a2967c1SBruce Richardson    """ Connect to socket and handle user input """
10772727e0dSBruce Richardson    prompt = ''  # this evaluates to false in conditions
1086a2967c1SBruce Richardson    sock = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET)
1096a2967c1SBruce Richardson    global CMDS
11072727e0dSBruce Richardson
11172727e0dSBruce Richardson    if os.isatty(sys.stdin.fileno()):
11272727e0dSBruce Richardson        prompt = '--> '
1136a2967c1SBruce Richardson        print("Connecting to " + path)
1146a2967c1SBruce Richardson    try:
1156a2967c1SBruce Richardson        sock.connect(path)
1166a2967c1SBruce Richardson    except OSError:
1176a2967c1SBruce Richardson        print("Error connecting to " + path)
1186a2967c1SBruce Richardson        sock.close()
119b95af194SBruce Richardson        # if socket exists but is bad, or if non-interactive just return
120b95af194SBruce Richardson        if os.path.exists(path) or not prompt:
121b95af194SBruce Richardson            return
122b95af194SBruce Richardson        # if user didn't give a valid socket path, but there are
123b95af194SBruce Richardson        # some sockets, help the user out by printing how to connect
124b95af194SBruce Richardson        socks = find_sockets(os.path.dirname(path))
125b95af194SBruce Richardson        if socks:
126b95af194SBruce Richardson            print("\nOther DPDK telemetry sockets found:")
127b95af194SBruce Richardson            print_socket_options(args.file_prefix, socks)
1289055bcdeSConor Walsh        else:
1299055bcdeSConor Walsh            list_fp()
1306a2967c1SBruce Richardson        return
131*66542840SChengwen Feng    json_reply = read_socket(sock, 1024, prompt, prompt)
1326a2967c1SBruce Richardson    output_buf_len = json_reply["max_output_len"]
133d786e30eSBruce Richardson    app_name = get_app_name(json_reply["pid"])
13472727e0dSBruce Richardson    if app_name and prompt:
135d786e30eSBruce Richardson        print('Connected to application: "%s"' % app_name)
1366a2967c1SBruce Richardson
1376a2967c1SBruce Richardson    # get list of commands for readline completion
1386a2967c1SBruce Richardson    sock.send("/".encode())
1396a2967c1SBruce Richardson    CMDS = read_socket(sock, output_buf_len, False)["/"]
1406a2967c1SBruce Richardson
1416a2967c1SBruce Richardson    # interactive prompt
142744e9a87SBruce Richardson    try:
14372727e0dSBruce Richardson        text = input(prompt).strip()
1446a2967c1SBruce Richardson        while text != "quit":
1456a2967c1SBruce Richardson            if text.startswith('/'):
1466a2967c1SBruce Richardson                sock.send(text.encode())
147*66542840SChengwen Feng                read_socket(sock, output_buf_len, pretty=prompt)
14872727e0dSBruce Richardson            text = input(prompt).strip()
149744e9a87SBruce Richardson    except EOFError:
150744e9a87SBruce Richardson        pass
151744e9a87SBruce Richardson    finally:
1526a2967c1SBruce Richardson        sock.close()
1536a2967c1SBruce Richardson
1546a2967c1SBruce Richardson
1556a2967c1SBruce Richardsondef readline_complete(text, state):
1566a2967c1SBruce Richardson    """ Find any matching commands from the list based on user input """
1576a2967c1SBruce Richardson    all_cmds = ['quit'] + CMDS
1586a2967c1SBruce Richardson    if text:
1596a2967c1SBruce Richardson        matches = [c for c in all_cmds if c.startswith(text)]
1606a2967c1SBruce Richardson    else:
1616a2967c1SBruce Richardson        matches = all_cmds
1626a2967c1SBruce Richardson    return matches[state]
1636a2967c1SBruce Richardson
1646a2967c1SBruce Richardson
1656a2967c1SBruce Richardsonreadline.parse_and_bind('tab: complete')
1666a2967c1SBruce Richardsonreadline.set_completer(readline_complete)
1676a2967c1SBruce Richardsonreadline.set_completer_delims(readline.get_completer_delims().replace('/', ''))
1686a2967c1SBruce Richardson
1692d9a697eSKevin Laatzparser = argparse.ArgumentParser()
170b95af194SBruce Richardsonparser.add_argument('-f', '--file-prefix', default=DEFAULT_PREFIX,
171ea3ef0b8SBruce Richardson                    help='Provide file-prefix for DPDK runtime directory')
17211435aaeSBruce Richardsonparser.add_argument('-i', '--instance', default='0', type=int,
1736839b8adSConor Walsh                    help='Provide instance number for DPDK application')
1749055bcdeSConor Walshparser.add_argument('-l', '--list', action="store_true", default=False,
1759055bcdeSConor Walsh                    help='List all possible file-prefixes and exit')
1762d9a697eSKevin Laatzargs = parser.parse_args()
1779055bcdeSConor Walshif args.list:
1789055bcdeSConor Walsh    list_fp()
1799055bcdeSConor Walsh    sys.exit(0)
180b95af194SBruce Richardsonsock_path = os.path.join(get_dpdk_runtime_dir(args.file_prefix), SOCKET_NAME)
18111435aaeSBruce Richardsonif args.instance > 0:
18211435aaeSBruce Richardson    sock_path += ":{}".format(args.instance)
183b95af194SBruce Richardsonhandle_socket(args, sock_path)
184