13f6f8362SLouise Kilheeney#! /usr/bin/env python3 26a2967c1SBruce Richardson# SPDX-License-Identifier: BSD-3-Clause 36a2967c1SBruce Richardson# Copyright(c) 2020 Intel Corporation 46a2967c1SBruce Richardson 56a2967c1SBruce Richardson""" 66a2967c1SBruce RichardsonScript to be used with V2 Telemetry. 76a2967c1SBruce RichardsonAllows the user input commands and read the Telemetry response. 86a2967c1SBruce Richardson""" 96a2967c1SBruce Richardson 106a2967c1SBruce Richardsonimport socket 116a2967c1SBruce Richardsonimport os 1272727e0dSBruce Richardsonimport sys 13b95af194SBruce Richardsonimport glob 146a2967c1SBruce Richardsonimport json 15d786e30eSBruce Richardsonimport errno 166a2967c1SBruce Richardsonimport readline 172d9a697eSKevin Laatzimport argparse 186a2967c1SBruce Richardson 196a2967c1SBruce Richardson# global vars 206a2967c1SBruce RichardsonTELEMETRY_VERSION = "v2" 21b95af194SBruce RichardsonSOCKET_NAME = 'dpdk_telemetry.{}'.format(TELEMETRY_VERSION) 22b95af194SBruce RichardsonDEFAULT_PREFIX = 'rte' 236a2967c1SBruce RichardsonCMDS = [] 246a2967c1SBruce Richardson 256a2967c1SBruce Richardson 26*66542840SChengwen Fengdef read_socket(sock, buf_len, echo=True, pretty=False): 276a2967c1SBruce Richardson """ Read data from socket and return it in JSON format """ 286a2967c1SBruce Richardson reply = sock.recv(buf_len).decode() 296a2967c1SBruce Richardson try: 306a2967c1SBruce Richardson ret = json.loads(reply) 316a2967c1SBruce Richardson except json.JSONDecodeError: 326a2967c1SBruce Richardson print("Error in reply: ", reply) 336a2967c1SBruce Richardson sock.close() 346a2967c1SBruce Richardson raise 356a2967c1SBruce Richardson if echo: 36*66542840SChengwen Feng indent = 2 if pretty else None 37*66542840SChengwen Feng print(json.dumps(ret, indent=indent)) 386a2967c1SBruce Richardson return ret 396a2967c1SBruce Richardson 406a2967c1SBruce Richardson 41d786e30eSBruce Richardsondef get_app_name(pid): 42d786e30eSBruce Richardson """ return the app name for a given PID, for printing """ 43d786e30eSBruce Richardson proc_cmdline = os.path.join('/proc', str(pid), 'cmdline') 44d786e30eSBruce Richardson try: 45d786e30eSBruce Richardson with open(proc_cmdline) as f: 46d786e30eSBruce Richardson argv0 = f.read(1024).split('\0')[0] 47d786e30eSBruce Richardson return os.path.basename(argv0) 48d786e30eSBruce Richardson except IOError as e: 49d786e30eSBruce Richardson # ignore file not found errors 50d786e30eSBruce Richardson if e.errno != errno.ENOENT: 51d786e30eSBruce Richardson raise 52d786e30eSBruce Richardson return None 53d786e30eSBruce Richardson 54d786e30eSBruce Richardson 55b95af194SBruce Richardsondef find_sockets(path): 56b95af194SBruce Richardson """ Find any possible sockets to connect to and return them """ 57b95af194SBruce Richardson return glob.glob(os.path.join(path, SOCKET_NAME + '*')) 58b95af194SBruce Richardson 59b95af194SBruce Richardson 60b95af194SBruce Richardsondef print_socket_options(prefix, paths): 61b95af194SBruce Richardson """ Given a set of socket paths, give the commands needed to connect """ 62b95af194SBruce Richardson cmd = sys.argv[0] 63b95af194SBruce Richardson if prefix != DEFAULT_PREFIX: 64b95af194SBruce Richardson cmd += " -f " + prefix 65b95af194SBruce Richardson for s in sorted(paths): 66b95af194SBruce Richardson sock_name = os.path.basename(s) 67b95af194SBruce Richardson if sock_name.endswith(TELEMETRY_VERSION): 68b95af194SBruce Richardson print("- {} # Connect with '{}'".format(os.path.basename(s), 69b95af194SBruce Richardson cmd)) 70b95af194SBruce Richardson else: 71b95af194SBruce Richardson print("- {} # Connect with '{} -i {}'".format(os.path.basename(s), 72b95af194SBruce Richardson cmd, 73b95af194SBruce Richardson s.split(':')[-1])) 74b95af194SBruce Richardson 75b95af194SBruce Richardson 769055bcdeSConor Walshdef get_dpdk_runtime_dir(fp): 779055bcdeSConor Walsh """ Using the same logic as in DPDK's EAL, get the DPDK runtime directory 789055bcdeSConor Walsh based on the file-prefix and user """ 791835a22fSStephen Hemminger run_dir = os.environ.get('RUNTIME_DIRECTORY') 801835a22fSStephen Hemminger if not run_dir: 819055bcdeSConor Walsh if (os.getuid() == 0): 821835a22fSStephen Hemminger run_dir = '/var/run' 831835a22fSStephen Hemminger else: 841835a22fSStephen Hemminger run_dir = os.environ.get('XDG_RUNTIME_DIR', '/tmp') 851835a22fSStephen Hemminger return os.path.join(run_dir, 'dpdk', fp) 869055bcdeSConor Walsh 879055bcdeSConor Walsh 889055bcdeSConor Walshdef list_fp(): 899055bcdeSConor Walsh """ List all available file-prefixes to user """ 909055bcdeSConor Walsh path = get_dpdk_runtime_dir('') 919055bcdeSConor Walsh sockets = glob.glob(os.path.join(path, "*", SOCKET_NAME + "*")) 929055bcdeSConor Walsh prefixes = [] 939055bcdeSConor Walsh if not sockets: 949055bcdeSConor Walsh print("No DPDK apps with telemetry enabled available") 959055bcdeSConor Walsh else: 969055bcdeSConor Walsh print("Valid file-prefixes:\n") 979055bcdeSConor Walsh for s in sockets: 989055bcdeSConor Walsh prefixes.append(os.path.relpath(os.path.dirname(s), start=path)) 999055bcdeSConor Walsh for p in sorted(set(prefixes)): 1009055bcdeSConor Walsh print(p) 1019055bcdeSConor Walsh print_socket_options(p, glob.glob(os.path.join(path, p, 1029055bcdeSConor Walsh SOCKET_NAME + "*"))) 1039055bcdeSConor Walsh 1049055bcdeSConor Walsh 105b95af194SBruce Richardsondef handle_socket(args, path): 1066a2967c1SBruce Richardson """ Connect to socket and handle user input """ 10772727e0dSBruce Richardson prompt = '' # this evaluates to false in conditions 1086a2967c1SBruce Richardson sock = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET) 1096a2967c1SBruce Richardson global CMDS 11072727e0dSBruce Richardson 11172727e0dSBruce Richardson if os.isatty(sys.stdin.fileno()): 11272727e0dSBruce Richardson prompt = '--> ' 1136a2967c1SBruce Richardson print("Connecting to " + path) 1146a2967c1SBruce Richardson try: 1156a2967c1SBruce Richardson sock.connect(path) 1166a2967c1SBruce Richardson except OSError: 1176a2967c1SBruce Richardson print("Error connecting to " + path) 1186a2967c1SBruce Richardson sock.close() 119b95af194SBruce Richardson # if socket exists but is bad, or if non-interactive just return 120b95af194SBruce Richardson if os.path.exists(path) or not prompt: 121b95af194SBruce Richardson return 122b95af194SBruce Richardson # if user didn't give a valid socket path, but there are 123b95af194SBruce Richardson # some sockets, help the user out by printing how to connect 124b95af194SBruce Richardson socks = find_sockets(os.path.dirname(path)) 125b95af194SBruce Richardson if socks: 126b95af194SBruce Richardson print("\nOther DPDK telemetry sockets found:") 127b95af194SBruce Richardson print_socket_options(args.file_prefix, socks) 1289055bcdeSConor Walsh else: 1299055bcdeSConor Walsh list_fp() 1306a2967c1SBruce Richardson return 131*66542840SChengwen Feng json_reply = read_socket(sock, 1024, prompt, prompt) 1326a2967c1SBruce Richardson output_buf_len = json_reply["max_output_len"] 133d786e30eSBruce Richardson app_name = get_app_name(json_reply["pid"]) 13472727e0dSBruce Richardson if app_name and prompt: 135d786e30eSBruce Richardson print('Connected to application: "%s"' % app_name) 1366a2967c1SBruce Richardson 1376a2967c1SBruce Richardson # get list of commands for readline completion 1386a2967c1SBruce Richardson sock.send("/".encode()) 1396a2967c1SBruce Richardson CMDS = read_socket(sock, output_buf_len, False)["/"] 1406a2967c1SBruce Richardson 1416a2967c1SBruce Richardson # interactive prompt 142744e9a87SBruce Richardson try: 14372727e0dSBruce Richardson text = input(prompt).strip() 1446a2967c1SBruce Richardson while text != "quit": 1456a2967c1SBruce Richardson if text.startswith('/'): 1466a2967c1SBruce Richardson sock.send(text.encode()) 147*66542840SChengwen Feng read_socket(sock, output_buf_len, pretty=prompt) 14872727e0dSBruce Richardson text = input(prompt).strip() 149744e9a87SBruce Richardson except EOFError: 150744e9a87SBruce Richardson pass 151744e9a87SBruce Richardson finally: 1526a2967c1SBruce Richardson sock.close() 1536a2967c1SBruce Richardson 1546a2967c1SBruce Richardson 1556a2967c1SBruce Richardsondef readline_complete(text, state): 1566a2967c1SBruce Richardson """ Find any matching commands from the list based on user input """ 1576a2967c1SBruce Richardson all_cmds = ['quit'] + CMDS 1586a2967c1SBruce Richardson if text: 1596a2967c1SBruce Richardson matches = [c for c in all_cmds if c.startswith(text)] 1606a2967c1SBruce Richardson else: 1616a2967c1SBruce Richardson matches = all_cmds 1626a2967c1SBruce Richardson return matches[state] 1636a2967c1SBruce Richardson 1646a2967c1SBruce Richardson 1656a2967c1SBruce Richardsonreadline.parse_and_bind('tab: complete') 1666a2967c1SBruce Richardsonreadline.set_completer(readline_complete) 1676a2967c1SBruce Richardsonreadline.set_completer_delims(readline.get_completer_delims().replace('/', '')) 1686a2967c1SBruce Richardson 1692d9a697eSKevin Laatzparser = argparse.ArgumentParser() 170b95af194SBruce Richardsonparser.add_argument('-f', '--file-prefix', default=DEFAULT_PREFIX, 171ea3ef0b8SBruce Richardson help='Provide file-prefix for DPDK runtime directory') 17211435aaeSBruce Richardsonparser.add_argument('-i', '--instance', default='0', type=int, 1736839b8adSConor Walsh help='Provide instance number for DPDK application') 1749055bcdeSConor Walshparser.add_argument('-l', '--list', action="store_true", default=False, 1759055bcdeSConor Walsh help='List all possible file-prefixes and exit') 1762d9a697eSKevin Laatzargs = parser.parse_args() 1779055bcdeSConor Walshif args.list: 1789055bcdeSConor Walsh list_fp() 1799055bcdeSConor Walsh sys.exit(0) 180b95af194SBruce Richardsonsock_path = os.path.join(get_dpdk_runtime_dir(args.file_prefix), SOCKET_NAME) 18111435aaeSBruce Richardsonif args.instance > 0: 18211435aaeSBruce Richardson sock_path += ":{}".format(args.instance) 183b95af194SBruce Richardsonhandle_socket(args, sock_path) 184