diff --git a/README.md b/README.md index 732893e..d226421 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ can be run directly. Usage: ``` -ps_mem [-h|--help] [-p PID,...] [-s|--split-args] [-t|--total] [-w N] +ps_mem [-h|--help] [-H [user@]host] [-p PID,...] [-s|--split-args] [-t|--total] [-w N] [-d|--discriminate-by-pid] [-S|--swap] ``` @@ -48,3 +48,17 @@ for i in $(ps -e -o user= | sort | uniq); do printf '%-20s%10s\n' $i $(sudo ps_mem --total -p $(pgrep -d, -u $i)) done ``` + +For embedded systems with no python on board with ssh access you could: + +```sh +ps_mem.py -H root@192.168.31.95 -p 15106 + + Private + Shared = RAM used Program + + 78.4 MiB + 1.5 MiB = 79.9 MiB cobalt_loader + +--------------------------------- + 79.9 MiB +================================= +``` diff --git a/ps_mem.1 b/ps_mem.1 index f2771c1..48d423a 100644 --- a/ps_mem.1 +++ b/ps_mem.1 @@ -28,6 +28,9 @@ Show help message \-\-version Show version info .TP +\-H [user@]host +Connect to the remote host via SSH (key-based auth is required) +.TP \-p PID[,PID2,...PIDN] Show memory consumption for the specified processes .TP diff --git a/ps_mem.py b/ps_mem.py index d03a987..a9dc12a 100755 --- a/ps_mem.py +++ b/ps_mem.py @@ -112,6 +112,39 @@ def write(self, data): def close(self): self.stream.close() +class SSHProc: + def __init__(self, host, proc): + self.host = host + self.proc = proc + + def path(self, *args): + return os.path.join(self.proc, *(str(a) for a in args)) + + def listdir(self, path): + import subprocess as sp + return sp.Popen(f"ssh {self.host} ls {path}".split(), + stdout=sp.PIPE, stdin=sp.DEVNULL, stderr=sp.DEVNULL, + encoding='utf-8').stdout.read().split() + + def exists(self, path): + import subprocess as sp + return sp.Popen(f"ssh {self.host} test -f {path}".split(), + stdout=sp.DEVNULL, stdin=sp.DEVNULL, stderr=sp.DEVNULL, + encoding='utf-8').wait() == 0 + + def readlink(self, path): + import subprocess as sp + return sp.Popen(f"ssh {self.host} readlink -f {path}".split(), + stdout=sp.PIPE, stdin=sp.DEVNULL, stderr=sp.DEVNULL, + encoding='utf-8').stdout.read() + + def open(self, *args): + import subprocess as sp + path = self.path(*args) + return sp.Popen(f"ssh {self.host} cat {path}".split(), + stdout=sp.PIPE, stdin=sp.DEVNULL, stderr=sp.DEVNULL, + encoding='utf-8').stdout + class Proc: def __init__(self): uname = os.uname() @@ -120,6 +153,15 @@ def __init__(self): else: self.proc = '/proc' + def listdir(self, path): + return os.listdir(path) + + def exists(self, path): + return os.path.exists(path) + + def readlink(self, path): + return os.readlink(path) + def path(self, *args): return os.path.join(self.proc, *(str(a) for a in args)) @@ -139,8 +181,6 @@ def open(self, *args): raise LookupError raise -proc = Proc() - # # Functions @@ -150,6 +190,12 @@ def parse_options(): help_msg = 'Show program core memory usage.' parser = argparse.ArgumentParser(prog='ps_mem', description=help_msg) parser.add_argument('--version', action='version', version='3.14') + parser.add_argument( + '-H', '--host', + dest='host', + action='store', + help='The host to SSH to', + ) parser.add_argument( '-s', '--split-args', action='store_true', @@ -199,6 +245,7 @@ def parse_options(): parser.error('Seconds must be positive! (%s)' % args.watch) return ( + args.host, args.split_args, args.pids_to_show, args.watch, @@ -244,9 +291,9 @@ def getMemStats(pid): Swap = 0 - if os.path.exists(proc.path(pid, 'smaps')): # stat + if proc.exists(proc.path(pid, 'smaps')): # stat smaps = 'smaps' - if os.path.exists(proc.path(pid, 'smaps_rollup')): + if proc.exists(proc.path(pid, 'smaps_rollup')): smaps = 'smaps_rollup' # faster to process lines = proc.open(pid, smaps).readlines() # open # Note we checksum smaps as maps is usually but @@ -308,7 +355,7 @@ def getCmdName(pid, split_args, discriminate_by_pid, exe_only=False): path = proc.path(pid, 'exe') try: - path = os.readlink(path) + path = proc.readlink(path) # Some symlink targets were seen to contain NULs on RHEL 5 at least # https://github.com/pixelb/scripts/pull/10, so take string up to NUL path = path.split('\0')[0] @@ -408,7 +455,7 @@ def val_accuracy(show_swap): return 1, swap_accuracy return 0, swap_accuracy elif kv[:2] == (2,6): - if os.path.exists(proc.path(pid, 'smaps')): + if proc.exists(proc.path(pid, 'smaps')): swap_accuracy = 1 if proc.open(pid, 'smaps').read().find("Pss:")!=-1: return 2, swap_accuracy @@ -417,7 +464,7 @@ def val_accuracy(show_swap): if (2,6,1) <= kv <= (2,6,9): return -1, swap_accuracy return 0, swap_accuracy - elif kv[0] > 2 and os.path.exists(proc.path(pid, 'smaps')): + elif kv[0] > 2 and proc.exists(proc.path(pid, 'smaps')): swap_accuracy = 1 if show_swap and proc.open(pid, 'smaps').read().find("SwapPss:")!=-1: swap_accuracy = 2 @@ -482,7 +529,7 @@ def get_memory_usage(pids_to_show, split_args, discriminate_by_pid, mem_ids = {} count = {} swaps = {} - for pid in os.listdir(proc.path('')): + for pid in proc.listdir(proc.path('')): if not pid.isdigit(): continue pid = int(pid) @@ -613,9 +660,15 @@ def main(): sys.stdout = Unbuffered(sys.stdout) sys.stderr = Unbuffered(sys.stderr) - split_args, pids_to_show, watch, only_total, discriminate_by_pid, \ + host, split_args, pids_to_show, watch, only_total, discriminate_by_pid, \ show_swap = parse_options() + global proc + if host: + proc = SSHProc(host, "/proc") + else: + proc = Proc() + verify_environment(pids_to_show) if not only_total: