xref: /dpdk/usertools/dpdk-telemetry.py (revision 30a1de105a5f40d77b344a891c4a68f79e815c43)
1#! /usr/bin/env python3
2# SPDX-License-Identifier: BSD-3-Clause
3# Copyright(c) 2020 Intel Corporation
4
5"""
6Script to be used with V2 Telemetry.
7Allows the user input commands and read the Telemetry response.
8"""
9
10import socket
11import os
12import sys
13import glob
14import json
15import errno
16import readline
17import argparse
18
19# global vars
20TELEMETRY_VERSION = "v2"
21SOCKET_NAME = 'dpdk_telemetry.{}'.format(TELEMETRY_VERSION)
22DEFAULT_PREFIX = 'rte'
23CMDS = []
24
25
26def read_socket(sock, buf_len, echo=True):
27    """ Read data from socket and return it in JSON format """
28    reply = sock.recv(buf_len).decode()
29    try:
30        ret = json.loads(reply)
31    except json.JSONDecodeError:
32        print("Error in reply: ", reply)
33        sock.close()
34        raise
35    if echo:
36        print(json.dumps(ret))
37    return ret
38
39
40def get_app_name(pid):
41    """ return the app name for a given PID, for printing """
42    proc_cmdline = os.path.join('/proc', str(pid), 'cmdline')
43    try:
44        with open(proc_cmdline) as f:
45            argv0 = f.read(1024).split('\0')[0]
46            return os.path.basename(argv0)
47    except IOError as e:
48        # ignore file not found errors
49        if e.errno != errno.ENOENT:
50            raise
51    return None
52
53
54def find_sockets(path):
55    """ Find any possible sockets to connect to and return them """
56    return glob.glob(os.path.join(path, SOCKET_NAME + '*'))
57
58
59def print_socket_options(prefix, paths):
60    """ Given a set of socket paths, give the commands needed to connect """
61    cmd = sys.argv[0]
62    if prefix != DEFAULT_PREFIX:
63        cmd += " -f " + prefix
64    for s in sorted(paths):
65        sock_name = os.path.basename(s)
66        if sock_name.endswith(TELEMETRY_VERSION):
67            print("- {}  # Connect with '{}'".format(os.path.basename(s),
68                                                     cmd))
69        else:
70            print("- {}  # Connect with '{} -i {}'".format(os.path.basename(s),
71                                                           cmd,
72                                                           s.split(':')[-1]))
73
74
75def get_dpdk_runtime_dir(fp):
76    """ Using the same logic as in DPDK's EAL, get the DPDK runtime directory
77    based on the file-prefix and user """
78    run_dir = os.environ.get('RUNTIME_DIRECTORY')
79    if not run_dir:
80        if (os.getuid() == 0):
81            run_dir = '/var/run'
82        else:
83            run_dir = os.environ.get('XDG_RUNTIME_DIR', '/tmp')
84    return os.path.join(run_dir, 'dpdk', fp)
85
86
87def list_fp():
88    """ List all available file-prefixes to user """
89    path = get_dpdk_runtime_dir('')
90    sockets = glob.glob(os.path.join(path, "*", SOCKET_NAME + "*"))
91    prefixes = []
92    if not sockets:
93        print("No DPDK apps with telemetry enabled available")
94    else:
95        print("Valid file-prefixes:\n")
96    for s in sockets:
97        prefixes.append(os.path.relpath(os.path.dirname(s), start=path))
98    for p in sorted(set(prefixes)):
99        print(p)
100        print_socket_options(p, glob.glob(os.path.join(path, p,
101                                                       SOCKET_NAME + "*")))
102
103
104def handle_socket(args, path):
105    """ Connect to socket and handle user input """
106    prompt = ''  # this evaluates to false in conditions
107    sock = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET)
108    global CMDS
109
110    if os.isatty(sys.stdin.fileno()):
111        prompt = '--> '
112        print("Connecting to " + path)
113    try:
114        sock.connect(path)
115    except OSError:
116        print("Error connecting to " + path)
117        sock.close()
118        # if socket exists but is bad, or if non-interactive just return
119        if os.path.exists(path) or not prompt:
120            return
121        # if user didn't give a valid socket path, but there are
122        # some sockets, help the user out by printing how to connect
123        socks = find_sockets(os.path.dirname(path))
124        if socks:
125            print("\nOther DPDK telemetry sockets found:")
126            print_socket_options(args.file_prefix, socks)
127        else:
128            list_fp()
129        return
130    json_reply = read_socket(sock, 1024, prompt)
131    output_buf_len = json_reply["max_output_len"]
132    app_name = get_app_name(json_reply["pid"])
133    if app_name and prompt:
134        print('Connected to application: "%s"' % app_name)
135
136    # get list of commands for readline completion
137    sock.send("/".encode())
138    CMDS = read_socket(sock, output_buf_len, False)["/"]
139
140    # interactive prompt
141    try:
142        text = input(prompt).strip()
143        while text != "quit":
144            if text.startswith('/'):
145                sock.send(text.encode())
146                read_socket(sock, output_buf_len)
147            text = input(prompt).strip()
148    except EOFError:
149        pass
150    finally:
151        sock.close()
152
153
154def readline_complete(text, state):
155    """ Find any matching commands from the list based on user input """
156    all_cmds = ['quit'] + CMDS
157    if text:
158        matches = [c for c in all_cmds if c.startswith(text)]
159    else:
160        matches = all_cmds
161    return matches[state]
162
163
164readline.parse_and_bind('tab: complete')
165readline.set_completer(readline_complete)
166readline.set_completer_delims(readline.get_completer_delims().replace('/', ''))
167
168parser = argparse.ArgumentParser()
169parser.add_argument('-f', '--file-prefix', default=DEFAULT_PREFIX,
170                    help='Provide file-prefix for DPDK runtime directory')
171parser.add_argument('-i', '--instance', default='0', type=int,
172                    help='Provide instance number for DPDK application')
173parser.add_argument('-l', '--list', action="store_true", default=False,
174                    help='List all possible file-prefixes and exit')
175args = parser.parse_args()
176if args.list:
177    list_fp()
178    sys.exit(0)
179sock_path = os.path.join(get_dpdk_runtime_dir(args.file_prefix), SOCKET_NAME)
180if args.instance > 0:
181    sock_path += ":{}".format(args.instance)
182handle_socket(args, sock_path)
183