github.com/rohankumardubey/proxyfs@v0.0.0-20210108201508-653efa9ab00e/bin/pfs_stat (about) 1 #!/usr/bin/env python 2 3 import json 4 import os.path 5 import re 6 import sys 7 import time 8 import argparse 9 import requests 10 import subprocess 11 12 # TODO: 13 # * humanize stats if humanize is installed 14 # * better print formatting; use tables if appropriate module is installed 15 16 CONFIG_FILE_PATHS = [ 17 "/opt/ss/etc/proxyfs.conf", 18 "/etc/proxyfsd/saioproxyfsd0.conf" 19 ] 20 21 PFSCONFJSON_PATHS = [ 22 "/opt/ss/bin/pfsconfjson", 23 "/home/swift/code/ProxyFS/bin/pfsconfjson" 24 ] 25 26 DEFAULT_CONFIG = { 27 "hostname": "localhost", 28 "port": 15346 29 } 30 31 32 def find_config_file_path(): 33 for file_path in CONFIG_FILE_PATHS: 34 if os.path.isfile(file_path): 35 return file_path 36 return None 37 38 39 def get_values_to_return(provided_hostname, provided_port, calculated_hostname, 40 calculated_port): 41 if provided_hostname is not None: 42 return_hostname = provided_hostname 43 else: 44 return_hostname = calculated_hostname 45 46 if provided_port is not None: 47 return_port = provided_port 48 else: 49 return_port = calculated_port 50 51 return return_hostname, return_port 52 53 54 def get_pfsconfjson_path(): 55 try: 56 path = subprocess.check_output("which pfsconfjsons", 57 stderr=subprocess.PIPE, shell=True) 58 return path.strip() 59 except: 60 pass 61 62 for file_path in PFSCONFJSON_PATHS: 63 if os.path.isfile(file_path): 64 return file_path 65 66 try: 67 paths = subprocess.check_output("find / -iname 'pfsconfjson'", 68 shell=True, 69 stderr=subprocess.PIPE).splitlines() 70 except subprocess.CalledProcessError as ex: 71 paths = [line for line in ex.output.splitlines() if 72 "Permission denied" not in line] 73 finally: 74 if len(paths) > 0: 75 return paths[0].strip() 76 77 return None 78 79 80 def get_config_hostname_and_port(config_file_path, pfsconfjson_path): 81 pfsconfjson_call = "{} {}".format(pfsconfjson_path, config_file_path) 82 config_json_str = subprocess.check_output(pfsconfjson_call, shell=True) 83 config = json.loads(config_json_str) 84 whoami = config["Cluster"]["WhoAmI"][0] 85 hostname = config[whoami]["PrivateIPAddr"][0] 86 port = int(config["HTTPServer"]["TCPPort"][0]) 87 return hostname, port 88 89 90 def get_hostname_and_port(provided_hostname, provided_port): 91 if provided_hostname is not None and provided_port is not None: 92 return provided_hostname, provided_port 93 94 config_file_path = find_config_file_path() 95 if config_file_path is None: 96 return get_values_to_return(provided_hostname, provided_port, 97 DEFAULT_CONFIG["hostname"], 98 DEFAULT_CONFIG["port"]) 99 100 pfsconfjson_path = get_pfsconfjson_path() 101 if pfsconfjson_path is None: 102 return get_values_to_return(provided_hostname, provided_port, 103 DEFAULT_CONFIG["hostname"], 104 DEFAULT_CONFIG["port"]) 105 106 try: 107 config_hostname, config_port = get_config_hostname_and_port( 108 config_file_path, pfsconfjson_path) 109 return get_values_to_return(provided_hostname, provided_port, 110 config_hostname, config_port) 111 except: 112 return get_values_to_return(provided_hostname, provided_port, 113 DEFAULT_CONFIG["hostname"], 114 DEFAULT_CONFIG["port"]) 115 116 117 def print_stats(s, exclude=[], show_all=False, diff=True): 118 exclude_re = None 119 if exclude: 120 exclude_re = '|'.join(exclude) 121 122 for k in sorted(s.keys()): 123 # Do not display the line if it is in an excluded category 124 # or if we're in diff mode and it is unchanged. 125 # Unless we specified -v; in that case, show everything. 126 hide = re.match(exclude_re, k) 127 hide = hide or (diff and not s[k]) 128 if show_all or not hide: 129 print("{: <90} {: >20}".format(k, s[k])) 130 131 132 def stat_diff(s1, s2): 133 diff = {} 134 for k in s2: 135 if k not in s1: 136 continue 137 diff[k] = s2[k] - s1[k] 138 139 return diff 140 141 142 def parse_stats(text): 143 stats = {} 144 for l in text.split("\n"): 145 if not l: 146 continue 147 148 # Are stats guaranteed to be positive integers? 149 match = re.match("(\S+)\s+(\d+)\s*$", l) 150 if match: 151 stats[match.group(1)] = int(match.group(2)) 152 else: 153 sys.stderr.write("WARNING: unparsed line: [%s]\n" % l) 154 155 return stats 156 157 158 def get_stats_from_proxyfsd(hostname, port): 159 r = requests.get("http://%s:%d/metrics" % (hostname, port)) 160 r.raise_for_status() 161 stats = r.json() 162 stats['timestamp'] = int(time.time()) 163 return stats 164 165 166 def read_stats_from_file(fn): 167 with open(fn) as f: 168 text = f.read() 169 stats = parse_stats(text) 170 return stats 171 172 173 if __name__ == '__main__': 174 parser = argparse.ArgumentParser(description='Show Proxyfs stats') 175 parser.add_argument('--verbose', '-v', action='store_true', 176 help='verbose output') 177 parser.add_argument('--hostname', '-H', type=str, default=None, 178 help='Hostname of proxyfs stat server (probably localhost)') 179 parser.add_argument('--port', '-p', type=int, default=None, 180 help='Port used by proxyfs stat server') 181 parser.add_argument('--file', '-f', type=str, 182 help='Read starting stats from a file') 183 parser.add_argument('cmd', type=str, nargs=argparse.REMAINDER, help='command to run') 184 185 args = parser.parse_args() 186 187 excludes = ['go_runtime', 'logging_level'] 188 189 hostname, port = get_hostname_and_port(args.hostname, args.port) 190 191 if args.cmd or args.file: 192 start_stats = None 193 if args.file: 194 start_stats = read_stats_from_file(args.file) 195 else: 196 start_stats = get_stats_from_proxyfsd(hostname, port) 197 198 if args.cmd: 199 subprocess.call(args.cmd) 200 201 end_stats = get_stats_from_proxyfsd(hostname, port) 202 diff = stat_diff(start_stats, end_stats) 203 204 if args.cmd: 205 print("\n--") 206 print_stats(diff, exclude=excludes, show_all=args.verbose) 207 else: 208 stats = get_stats_from_proxyfsd(hostname, port) 209 print_stats(stats, diff=False, exclude=excludes, show_all=args.verbose)