#!/usr/bin/python3 import argparse import collections import errno import os import pathlib import socket import sys with open("/proc/mounts", 'r') as f: mount = [ line.split()[1] for line in f if "sysfs" in line ] if len(mount) == 0: print("ERROR: sysfs is not mounted") sys.exit(1) sunrpc = pathlib.Path(mount[0]) / "kernel" / "sunrpc" if not sunrpc.is_dir(): print("ERROR: sysfs does not have sunrpc directory") sys.exit(1) def read_addr_file(path): try: with open(path, 'r') as f: return f.readline().strip() except: return "(enoent)" def write_addr_file(path, newaddr): with open(path, 'w') as f: f.write(newaddr) return read_addr_file(path) def read_info_file(path): res = collections.defaultdict(int) try: with open(path) as info: lines = [ l.split("=", 1) for l in info if "=" in l ] res.update({ key:int(val.strip()) for (key, val) in lines }) finally: return res class Xprt: def __init__(self, path): self.path = path self.name = path.stem.rsplit("-", 1)[0] self.type = path.stem.split("-")[2] self.info = read_info_file(path / "xprt_info") self.dstaddr = read_addr_file(path / "dstaddr") self.srcaddr = read_addr_file(path / "srcaddr") self.read_state() def __lt__(self, rhs): return self.name < rhs.name def _xprt(self): main = ", main" if self.info.get("main_xprt") else "" return f"{self.name}: {self.type}, {self.dstaddr}, " \ f"port {self.info['dst_port']}, state <{self.state}>{main}" def _src_reqs(self): return f" Source: {self.srcaddr}, port {self.info['src_port']}, " \ f"Requests: {self.info['num_reqs']}" def _cong_slots(self): return f" Congestion: cur {self.info['cur_cong']}, win {self.info['cong_win']}, " \ f"Slots: min {self.info['min_num_slots']}, max {self.info['max_num_slots']}" def _queues(self): return f" Queues: binding {self.info['binding_q_len']}, " \ f"sending {self.info['sending_q_len']}, pending {self.info['pending_q_len']}, " \ f"backlog {self.info['backlog_q_len']}, tasks {self.info['tasks_queuelen']}" def __str__(self): if not self.path.exists(): return f"{self.name}: has been removed" return "\n".join([self._xprt(), self._src_reqs(), self._cong_slots(), self._queues() ]) def read_state(self): if self.path.exists(): with open(self.path / "xprt_state") as f: self.state = ','.join(f.readline().split()[1:]) def small_str(self): main = " [main]" if self.info.get("main_xprt") else "" return f"{self.name}: {self.type}, {self.dstaddr}{main}" def set_dstaddr(self, newaddr): self.dstaddr = write_addr_file(self.path / "dstaddr", newaddr) def set_state(self, state): if self.info.get("main_xprt"): raise Exception(f"Main xprts cannot be set {state}") with open(self.path / "xprt_state", 'w') as f: f.write(state) self.read_state() def remove(self): if self.info.get("main_xprt"): raise Exception("Main xprts cannot be removed") self.set_state("offline") self.set_state("remove") def add_command(subparser): parser = subparser.add_parser("xprt", help="Commands for individual xprts") parser.set_defaults(func=Xprt.show, xprt=None) subparser = parser.add_subparsers() remove = subparser.add_parser("remove", help="Remove an xprt") remove.add_argument("xprt", metavar="XPRT", nargs=1, help="Name of the xprt to remove") remove.set_defaults(func=Xprt.set_property, property="remove") show = subparser.add_parser("show", help="Show xprts") show.add_argument("xprt", metavar="XPRT", nargs='?', help="Name of a specific xprt to show") show.set_defaults(func=Xprt.show) set = subparser.add_parser("set", help="Change an xprt property") set.add_argument("xprt", metavar="XPRT", nargs=1, help="Name of a specific xprt to modify") subparser = set.add_subparsers(required=True) online = subparser.add_parser("online", help="Set an xprt online") online.set_defaults(func=Xprt.set_property, property="online") offline = subparser.add_parser("offline", help="Set an xprt offline") offline.set_defaults(func=Xprt.set_property, property="offline") dstaddr = subparser.add_parser("dstaddr", help="Change an xprt's dstaddr") dstaddr.add_argument("newaddr", metavar="NEWADDR", nargs=1, help="The new address for the xprt") dstaddr.set_defaults(func=Xprt.set_property, property="dstaddr") def get_by_name(name): glob = f"**/{name}-*" if name else "**/xprt-*" res = [ Xprt(x) for x in (sunrpc / "xprt-switches").glob(glob) ] if name and len(res) == 0: raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), f"{sunrpc / 'xprt-switches' / glob}") return sorted(res) def show(args): for xprt in Xprt.get_by_name(args.xprt): print(xprt) def set_property(args): for xprt in Xprt.get_by_name(args.xprt[0]): if args.property == "dstaddr": xprt.set_dstaddr(socket.gethostbyname(args.newaddr[0])) elif args.property == "remove": xprt.remove() else: xprt.set_state(args.property) print(xprt) class XprtSwitch: def __init__(self, path, sep=":"): self.path = path self.name = path.stem self.info = read_info_file(path / "xprt_switch_info") self.xprts = sorted([ Xprt(p) for p in self.path.iterdir() if p.is_dir() ]) self.sep = sep def __lt__(self, rhs): return self.name < rhs.name def __str__(self): switch = f"{self.name}{self.sep} " \ f"xprts {self.info['num_xprts']}, " \ f"active {self.info['num_active']}, " \ f"queue {self.info['queue_len']}" xprts = [ f" {x.small_str()}" for x in self.xprts ] return "\n".join([ switch ] + xprts) def add_command(subparser): parser = subparser.add_parser("switch", help="Commands for xprt switches") parser.set_defaults(func=XprtSwitch.show, switch=None) subparser = parser.add_subparsers() show = subparser.add_parser("show", help="Show xprt switches") show.add_argument("switch", metavar="SWITCH", nargs='?', help="Name of a specific switch to show") show.set_defaults(func=XprtSwitch.show) set = subparser.add_parser("set", help="Change an xprt switch property") set.add_argument("switch", metavar="SWITCH", nargs=1, help="Name of a specific xprt switch to modify") subparser = set.add_subparsers(required=True) dstaddr = subparser.add_parser("dstaddr", help="Change an xprt switch's dstaddr") dstaddr.add_argument("newaddr", metavar="NEWADDR", nargs=1, help="The new address for the xprt switch") dstaddr.set_defaults(func=XprtSwitch.set_property, property="dstaddr") def get_by_name(name): xprt_switches = sunrpc / "xprt-switches" if name: return [ XprtSwitch(xprt_switches / name) ] return [ XprtSwitch(f) for f in sorted(xprt_switches.iterdir()) ] def show(args): for switch in XprtSwitch.get_by_name(args.switch): print(switch) def set_property(args): for switch in XprtSwitch.get_by_name(args.switch[0]): resolved = socket.gethostbyname(args.newaddr[0]) for xprt in switch.xprts: xprt.set_dstaddr(resolved) print(switch) class RpcClient: def __init__(self, path): self.path = path self.name = path.stem self.switch = XprtSwitch(path / (path / "switch").readlink(), sep=",") def __lt__(self, rhs): return self.name < rhs.name def __str__(self): return f"{self.name}: {self.switch}" def add_command(subparser): parser = subparser.add_parser("client", help="Commands for rpc clients") parser.set_defaults(func=RpcClient.show, client=None) subparser = parser.add_subparsers() show = subparser.add_parser("show", help="Show rpc clients") show.add_argument("client", metavar="CLIENT", nargs='?', help="Name of a specific rpc client to show") parser.set_defaults(func=RpcClient.show) def get_by_name(name): rpc_clients = sunrpc / "rpc-clients" if name: return [ RpcClient(rpc_clients / name) ] return [ RpcClient(f) for f in sorted(rpc_clients.iterdir()) ] def show(args): for client in RpcClient.get_by_name(args.client): print(client) parser = argparse.ArgumentParser() def show_small_help(args): parser.print_usage() print("sunrpc dir:", sunrpc) parser.set_defaults(func=show_small_help) subparser = parser.add_subparsers(title="commands") RpcClient.add_command(subparser) XprtSwitch.add_command(subparser) Xprt.add_command(subparser) args = parser.parse_args() try: args.func(args) except Exception as e: print(str(e)) sys.exit(1)