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 if (os.getuid() == 0): 79 return os.path.join('/var/run/dpdk', fp) 80 return os.path.join(os.environ.get('XDG_RUNTIME_DIR', '/tmp'), 'dpdk', fp) 81 82 83def list_fp(): 84 """ List all available file-prefixes to user """ 85 path = get_dpdk_runtime_dir('') 86 sockets = glob.glob(os.path.join(path, "*", SOCKET_NAME + "*")) 87 prefixes = [] 88 if not sockets: 89 print("No DPDK apps with telemetry enabled available") 90 else: 91 print("Valid file-prefixes:\n") 92 for s in sockets: 93 prefixes.append(os.path.relpath(os.path.dirname(s), start=path)) 94 for p in sorted(set(prefixes)): 95 print(p) 96 print_socket_options(p, glob.glob(os.path.join(path, p, 97 SOCKET_NAME + "*"))) 98 99 100def handle_socket(args, path): 101 """ Connect to socket and handle user input """ 102 prompt = '' # this evaluates to false in conditions 103 sock = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET) 104 global CMDS 105 106 if os.isatty(sys.stdin.fileno()): 107 prompt = '--> ' 108 print("Connecting to " + path) 109 try: 110 sock.connect(path) 111 except OSError: 112 print("Error connecting to " + path) 113 sock.close() 114 # if socket exists but is bad, or if non-interactive just return 115 if os.path.exists(path) or not prompt: 116 return 117 # if user didn't give a valid socket path, but there are 118 # some sockets, help the user out by printing how to connect 119 socks = find_sockets(os.path.dirname(path)) 120 if socks: 121 print("\nOther DPDK telemetry sockets found:") 122 print_socket_options(args.file_prefix, socks) 123 else: 124 list_fp() 125 return 126 json_reply = read_socket(sock, 1024, prompt) 127 output_buf_len = json_reply["max_output_len"] 128 app_name = get_app_name(json_reply["pid"]) 129 if app_name and prompt: 130 print('Connected to application: "%s"' % app_name) 131 132 # get list of commands for readline completion 133 sock.send("/".encode()) 134 CMDS = read_socket(sock, output_buf_len, False)["/"] 135 136 # interactive prompt 137 try: 138 text = input(prompt).strip() 139 while text != "quit": 140 if text.startswith('/'): 141 sock.send(text.encode()) 142 read_socket(sock, output_buf_len) 143 text = input(prompt).strip() 144 except EOFError: 145 pass 146 finally: 147 sock.close() 148 149 150def readline_complete(text, state): 151 """ Find any matching commands from the list based on user input """ 152 all_cmds = ['quit'] + CMDS 153 if text: 154 matches = [c for c in all_cmds if c.startswith(text)] 155 else: 156 matches = all_cmds 157 return matches[state] 158 159 160readline.parse_and_bind('tab: complete') 161readline.set_completer(readline_complete) 162readline.set_completer_delims(readline.get_completer_delims().replace('/', '')) 163 164parser = argparse.ArgumentParser() 165parser.add_argument('-f', '--file-prefix', default=DEFAULT_PREFIX, 166 help='Provide file-prefix for DPDK runtime directory') 167parser.add_argument('-i', '--instance', default='0', type=int, 168 help='Provide instance number for DPDK application') 169parser.add_argument('-l', '--list', action="store_true", default=False, 170 help='List all possible file-prefixes and exit') 171args = parser.parse_args() 172if args.list: 173 list_fp() 174 sys.exit(0) 175sock_path = os.path.join(get_dpdk_runtime_dir(args.file_prefix), SOCKET_NAME) 176if args.instance > 0: 177 sock_path += ":{}".format(args.instance) 178handle_socket(args, sock_path) 179