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