来源:http://www.pixelbeat.org/scripts/ps_mem.py
mem.py:
#!/usr/bin/env python # Try to determine how much RAM is currently being used per program. # Note per _program_, not per process. So for example this script # will report RAM used by all httpd process together. In detail it reports: # sum(private RAM for program processes) + sum(Shared RAM for program processes) # The shared RAM is problematic to calculate, and this script automatically # selects the most accurate method available for your kernel. # Licence: LGPLv2 # Author: P@draigBrady.com # Source: http://www.pixelbeat.org/scripts/ps_mem.py # V1.0 06 Jul 2005 Initial release # V1.1 11 Aug 2006 root permission required for accuracy # V1.2 08 Nov 2006 Add total to output # Use KiB,MiB,... for units rather than K,M,... # V1.3 22 Nov 2006 Ignore shared col from /proc/$pid/statm for # 2.6 kernels up to and including 2.6.9. # There it represented the total file backed extent # V1.4 23 Nov 2006 Remove total from output as it's meaningless # (the shared values overlap with other programs). # Display the shared column. This extra info is # useful, especially as it overlaps between programs. # V1.5 26 Mar 2007 Remove redundant recursion from human() # V1.6 05 Jun 2007 Also report number of processes with a given name. # Patch from riccardo.murri@gmail.com # V1.7 20 Sep 2007 Use PSS from /proc/$pid/smaps if available, which # fixes some over-estimation and allows totalling. # Enumerate the PIDs directly rather than using ps, # which fixes the possible race between reading # RSS with ps, and shared memory with this program. # Also we can show non truncated command names. # V1.8 28 Sep 2007 More accurate matching for stats in /proc/$pid/smaps # as otherwise could match libraries causing a crash. # Patch from patrice.bouchand.fedora@gmail.com # V1.9 20 Feb 2008 Fix invalid values reported when PSS is available. # Reported by Andrey Borzenkov <arvidjaar@mail.ru> # V3.13 17 Sep 2018 # http://github.com/pixelb/scripts/commits/master/scripts/ps_mem.py # Notes: # # All interpreted programs where the interpreter is started # by the shell or with env, will be merged to the interpreter # (as that's what's given to exec). For e.g. all python programs # starting with "#!/usr/bin/env python" will be grouped under python. # You can change this by using the full command line but that will # have the undesirable affect of splitting up programs started with # differing parameters (for e.g. mingetty tty[1-6]). # # For 2.6 kernels up to and including 2.6.13 and later 2.4 redhat kernels # (rmap vm without smaps) it can not be accurately determined how many pages # are shared between processes in general or within a program in our case: # http://lkml.org/lkml/2005/7/6/250 # A warning is printed if overestimation is possible. # In addition for 2.6 kernels up to 2.6.9 inclusive, the shared # value in /proc/$pid/statm is the total file-backed extent of a process. # We ignore that, introducing more overestimation, again printing a warning. # Since kernel 2.6.23-rc8-mm1 PSS is available in smaps, which allows # us to calculate a more accurate value for the total RAM used by programs. # # Programs that use CLONE_VM without CLONE_THREAD are discounted by assuming # they're the only programs that have the same /proc/$PID/smaps file for # each instance. This will fail if there are multiple real instances of a # program that then use CLONE_VM without CLONE_THREAD, or if a clone changes # its memory map while we're checksumming each /proc/$PID/smaps. # # I don't take account of memory allocated for a program # by other programs. For e.g. memory used in the X server for # a program could be determined, but is not. # # FreeBSD is supported if linprocfs is mounted at /compat/linux/proc/ # FreeBSD 8.0 supports up to a level of Linux 2.6.16 import argparse import errno import os import sys import time # The following exits cleanly on Ctrl-C or EPIPE # while treating other exceptions as before. def std_exceptions(etype, value, tb): sys.excepthook = sys.__excepthook__ if issubclass(etype, KeyboardInterrupt): pass elif issubclass(etype, IOError) and value.errno == errno.EPIPE: pass else: sys.__excepthook__(etype, value, tb) sys.excepthook = std_exceptions # # Define some global variables # PAGESIZE = os.sysconf("SC_PAGE_SIZE") / 1024 #KiB our_pid = os.getpid() have_pss = 0 have_swap_pss = 0 class Unbuffered(object): def __init__(self, stream): self.stream = stream def write(self, data): self.stream.write(data) self.stream.flush() def close(self): self.stream.close() def flush(self): self.stream.flush() class Proc: def __init__(self): uname = os.uname() if uname[0] == "FreeBSD": self.proc = '/compat/linux/proc' else: self.proc = '/proc' def path(self, *args): return os.path.join(self.proc, *(str(a) for a in args)) def open(self, *args): try: if sys.version_info < (3,): return open(self.path(*args)) else: return open(self.path(*args), errors='ignore') except (IOError, OSError): if type(args[0]) is not int: raise val = sys.exc_info()[1] if (val.errno == errno.ENOENT or # kernel thread or process gone val.errno == errno.EPERM or val.errno == errno.EACCES): raise LookupError raise proc = Proc() # # Functions # 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.13') parser.add_argument( '-s', '--split-args', action='store_true', help='Show and separate by, all command line arguments', ) parser.add_argument( '-t', '--total', dest='only_total', action='store_true', help='Show only the total value', ) parser.add_argument( '-d', '--discriminate-by-pid', action='store_true', help='Show by process rather than by program', ) parser.add_argument( '-S', '--swap', dest='show_swap', action='store_true', help='Show swap information', ) parser.add_argument( '-p', dest='pids', metavar='<pid>[,pid2,...pidN]', help='Only show memory usage PIDs in the specified list', ) parser.add_argument( '-w', dest='watch', metavar='<N>', type=int, help='Measure and show process memory every N seconds', ) args = parser.parse_args() args.pids_to_show = [] if args.pids: try: args.pids_to_show = [int(x) for x in args.pids.split(',')] except ValueError: parser.error('Invalid PID(s): %s' % args.pids) if args.watch is not None: if args.watch <= 0: parser.error('Seconds must be positive! (%s)' % args.watch) return ( args.split_args, args.pids_to_show, args.watch, args.only_total, args.discriminate_by_pid, args.show_swap, ) # (major,minor,release) def kernel_ver(): kv = proc.open('sys/kernel/osrelease').readline().split(".")[:3] last = len(kv) if last == 2: kv.append('0') last -= 1 while last > 0: for char in "-_": kv[last] = kv[last].split(char)[0] try: int(kv[last]) except: kv[last] = 0 last -= 1 return (int(kv[0]), int(kv[1]), int(kv[2])) #return Private,Shared,Swap(Pss),unique_id #Note shared is always a subset of rss (trs is not always) def getMemStats(pid): global have_pss global have_swap_pss mem_id = pid #unique Private_lines = [] Shared_lines = [] Private_huge_lines = [] Shared_huge_lines = [] Pss_lines = [] Rss = (int(proc.open(pid, 'statm').readline().split()[1]) * PAGESIZE) Swap_lines = [] Swap_pss_lines = [] Swap = 0 if os.path.exists(proc.path(pid, 'smaps')): # stat smaps = 'smaps' if os.path.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 # not always different for separate processes. mem_id = hash(''.join(lines)) for line in lines: # {Private,Shared}_Hugetlb is not included in Pss (why?) # so we need to account for separately. if line.startswith("Private_Hugetlb:"): Private_huge_lines.append(line) elif line.startswith("Shared_Hugetlb:"): Shared_huge_lines.append(line) elif line.startswith("Shared"): Shared_lines.append(line) elif line.startswith("Private"): Private_lines.append(line) elif line.startswith("Pss:"): have_pss = 1 Pss_lines.append(line) elif line.startswith("Swap:"): Swap_lines.append(line) elif line.startswith("SwapPss:"): have_swap_pss = 1 Swap_pss_lines.append(line) Shared = sum([int(line.split()[1]) for line in Shared_lines]) Private = sum([int(line.split()[1]) for line in Private_lines]) Shared_huge = sum([int(line.split()[1]) for line in Shared_huge_lines]) Private_huge = sum([int(line.split()[1]) for line in Private_huge_lines]) #Note Shared + Private = Rss above #The Rss in smaps includes video card mem etc. if have_pss: pss_adjust = 0.5 # add 0.5KiB as this avg error due to truncation Pss = sum([float(line.split()[1])+pss_adjust for line in Pss_lines]) Shared = Pss - Private Private += Private_huge # Add after as PSS doesn't a/c for huge pages if have_swap_pss: # The kernel supports SwapPss, that shows proportional swap share. # Note that Swap - SwapPss is not Private Swap. Swap = sum([int(line.split()[1]) for line in Swap_pss_lines]) else: # Note that Swap = Private swap + Shared swap. Swap = sum([int(line.split()[1]) for line in Swap_lines]) elif (2,6,1) <= kernel_ver() <= (2,6,9): Shared = 0 #lots of overestimation, but what can we do? Shared_huge = 0 Private = Rss else: Shared = int(proc.open(pid, 'statm').readline().split()[2]) Shared *= PAGESIZE Shared_huge = 0 Private = Rss - Shared return (Private, Shared, Shared_huge, Swap, mem_id) def getCmdName(pid, split_args, discriminate_by_pid, exe_only=False): cmdline = proc.open(pid, 'cmdline').read().split("