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