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