Loading

ps_mem.py

  1. #!/usr/bin/env python
  2.  
  3.  
  4. # Try to determine how much RAM is currently being used per program.
  5. # Note the per program, not per process. So for example this script
  6. # will report mem used by all httpd process together. In detail it reports:
  7. # sum(all RSS for process instances) + max(shared mem for any process instance)
  8. #
  9. # The shared calculation below will factor out shared text and
  10. # libs etc. within a program, but not between programs. So there
  11. # will always be some overestimation. This will be the same for
  12. # all processes that just use libc for e.g. but more for others
  13. # that use larger shared libs like gnome, kde etc.
  14.  
  15.  
  16. # Author: P@draigBrady.com
  17.  
  18. # V1.0      06 Jul 2005    Initial release
  19. # V1.1      11 Aug 2006    root permission required for accuracy
  20. # V1.2      08 Nov 2006    Add total to output
  21. #                          Use KiB,MiB,... for units rather than K,M,...
  22. # V1.3      22 Nov 2006    Ignore shared col from /proc/$pid/statm for
  23. #                          2.6 kernels up to and including 2.6.9.
  24. #                          There it represented the total file backed extent
  25. # V1.4      23 Nov 2006    Remove total from output as it's meaningless
  26. #                          (the shared values overlap with other programs).
  27. #                          Display the shared column. This extra info is
  28. #                          useful, especially as it overlaps between programs.
  29. # V1.5      26 Mar 2007    Remove redundant recursion from human()
  30. # V1.6      05 Jun 2007    Also report number of processes with a given name.
  31. #                          Patch from riccardo.murri@gmail.com
  32.  
  33. # Notes:
  34. #
  35. # All interpreted programs where the interpreter is started
  36. # by the shell or with env, will be merged to the interpreter
  37. # (as that's what's given to exec). For e.g. all python programs
  38. # starting with "#!/usr/bin/env python" will be grouped under python.
  39. # You can change this by changing comm= to args= below but that will
  40. # have the undesirable affect of splitting up programs started with
  41. # differing parameters (for e.g. mingetty tty[1-6]).
  42. #
  43. # For 2.6 kernels up to and including 2.6.13 and later 2.4 redhat kernels
  44. # (rmap vm without smaps) it can not be accurately determined how many pages
  45. # are shared between processes in general or within a program in our case:
  46. # http://lkml.org/lkml/2005/7/6/250
  47. # A warning is printed if overestimation is possible.
  48. # In addition for 2.6 kernels up to 2.6.9 inclusive, the shared
  49. # value in /proc/$pid/statm is the total file-backed extent of a process.
  50. # We ignore that, introducing more overestimation, again printing a warning.
  51. #
  52. # I don't take account of memory allocated for a program
  53. # by other programs. For e.g. memory used in the X server for
  54. # a program could be determined, but is not.
  55. #
  56. # This script assumes threads are already merged by ps
  57.  
  58. # TODO:
  59. #
  60. # use ps just to enumerate the pids and names
  61. # so as to remove the race between reading rss and shared values
  62.  
  63. import sys, os, string
  64.  
  65. if os.geteuid() != 0:
  66.     sys.stderr.write("Sorry, root permission required.\n");
  67.     sys.exit(1)
  68.  
  69. PAGESIZE=os.sysconf("SC_PAGE_SIZE")/1024 #KiB
  70. our_pid=os.getpid()
  71.  
  72. #(major,minor,release)
  73. def kernel_ver():
  74.     kv=open("/proc/sys/kernel/osrelease").readline().split(".")[:3]
  75.     for char in "-_":
  76.         kv[2]=kv[2].split(char)[0]
  77.     return (int(kv[0]), int(kv[1]), int(kv[2]))
  78.  
  79. kv=kernel_ver()
  80.  
  81. def getShared(pid):
  82.     if os.path.exists("/proc/"+str(pid)+"/smaps"):
  83.         shared_lines=[line
  84.                       for line in open("/proc/"+str(pid)+"/smaps").readlines()
  85.                       if line.find("Shared")!=-1]
  86.         return sum([int(line.split()[1]) for line in shared_lines])
  87.     elif (2,6,1) <= kv <= (2,6,9):
  88.         return 0 #lots of overestimation, but what can we do?
  89.     else:
  90.         return int(open("/proc/"+str(pid)+"/statm").readline().split()[2])*PAGESIZE
  91.  
  92. cmds={}
  93. shareds={}
  94. count={}
  95. for line in os.popen("ps -e -o rss=,pid=,comm=").readlines():
  96.     size, pid, cmd = map(string.strip,line.strip().split(None,2))
  97.     if int(pid) == our_pid:
  98.         continue #no point counting this process
  99.     try:
  100.         shared=getShared(pid)
  101.     except:
  102.         continue #ps gone away
  103.     if shareds.get(cmd):
  104.         if shareds[cmd] < shared:
  105.             shareds[cmd]=shared
  106.     else:
  107.         shareds[cmd]=shared
  108.     #Note shared is always a subset of rss (trs is not always)
  109.     cmds[cmd]=cmds.setdefault(cmd,0)+int(size)-shared
  110.     if count.has_key(cmd):
  111.       count[cmd] += 1
  112.     else:
  113.       count[cmd] = 1
  114.  
  115. #Add max shared mem for each program
  116. for cmd in cmds.keys():
  117.     cmds[cmd]=cmds[cmd]+shareds[cmd]
  118.  
  119. sort_list = cmds.items()
  120. sort_list.sort(lambda x,y:cmp(x[1],y[1]))
  121. sort_list=filter(lambda x:x[1],sort_list) #get rid of zero sized processes (kernel threads)
  122.  
  123. #The following matches "du -h" output
  124. #see also human.py
  125. def human(num, power="Ki"):
  126.     powers=["Ki","Mi","Gi","Ti"]
  127.     while num >= 1000: #4 digits
  128.         num /= 1024.0
  129.         power=powers[powers.index(power)+1]
  130.     return "%.1f %s" % (num,power)
  131.  
  132. def cmd_with_count(cmd, count):
  133.     if count>1:
  134.       return "%s (%u)" % (cmd, count)
  135.     else:
  136.       return cmd
  137. print " Private  +  Shared  =  RAM used\tProgram \n"
  138. for cmd in sort_list:
  139.     print "%8sB + %8sB = %8sB\t%s" % (human(cmd[1]-shareds[cmd[0]]), human(shareds[cmd[0]]), human(cmd[1]),
  140.                                       cmd_with_count(cmd[0], count[cmd[0]]))
  141. print "\n Private  +  Shared  =  RAM used\tProgram \n"
  142.  
  143. #Warn of possible inaccuracies
  144. #1 = accurate
  145. #0 = some shared mem not reported
  146. #-1= all shared mem not reported
  147. def shared_val_accurate():
  148.     """http://wiki.apache.org/spamassassin/TopSharedMemoryBug"""
  149.     if kv[:2] == (2,4):
  150.         if open("/proc/meminfo").read().find("Inact_") == -1:
  151.             return 1
  152.         return 0
  153.     elif kv[:2] == (2,6):
  154.         if os.path.exists("/proc/"+str(os.getpid())+"/smaps"):
  155.             return 1
  156.         if (2,6,1) <= kv <= (2,6,9):
  157.             return -1
  158.         return 0
  159.     else:
  160.         return 1
  161.  
  162. vm_accuracy = shared_val_accurate()
  163. if vm_accuracy == -1:
  164.     sys.stderr.write("Warning: Shared memory is not reported by this system.\n")
  165.     sys.stderr.write("Values reported will be too large.\n")
  166. elif vm_accuracy == 0:
  167.     sys.stderr.write("Warning: Shared memory is not reported accurately by this system.\n")
  168.     sys.stderr.write("Values reported could be too large.\n")

Comments