Guest User

Untitled

a guest
Jan 29th, 2013
901
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 13.04 KB | None | 0 0
  1. #!/usr/bin/env python
  2.  
  3. # Try to determine how much RAM is currently being used per program.
  4. # Note per _program_, not per process. So for example this script
  5. # will report RAM used by all httpd process together. In detail it reports:
  6. # sum(private RAM for program processes) + sum(Shared RAM for program processes)
  7. # The shared RAM is problematic to calculate, and this script automatically
  8. # selects the most accurate method available for your kernel.
  9.  
  10. # Source: http://www.pixelbeat.org/scripts/ps_mem.py
  11.  
  12. # V1.0 06 Jul 2005 Initial release
  13. # V1.1 11 Aug 2006 root permission required for accuracy
  14. # V1.2 08 Nov 2006 Add total to output
  15. # Use KiB,MiB,... for units rather than K,M,...
  16. # V1.3 22 Nov 2006 Ignore shared col from /proc/$pid/statm for
  17. # 2.6 kernels up to and including 2.6.9.
  18. # There it represented the total file backed extent
  19. # V1.4 23 Nov 2006 Remove total from output as it's meaningless
  20. # (the shared values overlap with other programs).
  21. # Display the shared column. This extra info is
  22. # useful, especially as it overlaps between programs.
  23. # V1.5 26 Mar 2007 Remove redundant recursion from human()
  24. # V1.6 05 Jun 2007 Also report number of processes with a given name.
  25. # Patch from [email protected]
  26. # V1.7 20 Sep 2007 Use PSS from /proc/$pid/smaps if available, which
  27. # fixes some over-estimation and allows totalling.
  28. # Enumerate the PIDs directly rather than using ps,
  29. # which fixes the possible race between reading
  30. # RSS with ps, and shared memory with this program.
  31. # Also we can show non truncated command names.
  32. # V1.8 28 Sep 2007 More accurate matching for stats in /proc/$pid/smaps
  33. # as otherwise could match libraries causing a crash.
  34. # Patch from [email protected]
  35. # V1.9 20 Feb 2008 Fix invalid values reported when PSS is available.
  36. # Reported by Andrey Borzenkov <[email protected]>
  37. # V2.6 13 Aug 2011
  38. # http://github.com/pixelb/scripts/commits/master/scripts/ps_mem.py
  39.  
  40. # Notes:
  41. #
  42. # All interpreted programs where the interpreter is started
  43. # by the shell or with env, will be merged to the interpreter
  44. # (as that's what's given to exec). For e.g. all python programs
  45. # starting with "#!/usr/bin/env python" will be grouped under python.
  46. # You can change this by using the full command line but that will
  47. # have the undesirable affect of splitting up programs started with
  48. # differing parameters (for e.g. mingetty tty[1-6]).
  49. #
  50. # For 2.6 kernels up to and including 2.6.13 and later 2.4 redhat kernels
  51. # (rmap vm without smaps) it can not be accurately determined how many pages
  52. # are shared between processes in general or within a program in our case:
  53. # http://lkml.org/lkml/2005/7/6/250
  54. # A warning is printed if overestimation is possible.
  55. # In addition for 2.6 kernels up to 2.6.9 inclusive, the shared
  56. # value in /proc/$pid/statm is the total file-backed extent of a process.
  57. # We ignore that, introducing more overestimation, again printing a warning.
  58. # Since kernel 2.6.23-rc8-mm1 PSS is available in smaps, which allows
  59. # us to calculate a more accurate value for the total RAM used by programs.
  60. #
  61. # Programs that use CLONE_VM without CLONE_THREAD are discounted by assuming
  62. # they're the only programs that have the same /proc/$PID/smaps file for
  63. # each instance. This will fail if there are multiple real instances of a
  64. # program that then use CLONE_VM without CLONE_THREAD, or if a clone changes
  65. # its memory map while we're checksumming each /proc/$PID/smaps.
  66. #
  67. # I don't take account of memory allocated for a program
  68. # by other programs. For e.g. memory used in the X server for
  69. # a program could be determined, but is not.
  70. #
  71. # FreeBSD is supported if linprocfs is mounted at /compat/linux/proc/
  72. # FreeBSD 8.0 supports up to a level of Linux 2.6.16
  73.  
  74. import errno
  75. import os
  76. import sys
  77.  
  78. try:
  79. # md5 module is deprecated on python 2.6
  80. # so try the newer hashlib first
  81. import hashlib
  82. md5_new = hashlib.md5
  83. except ImportError:
  84. import md5
  85. md5_new = md5.new
  86.  
  87.  
  88. # The following exits cleanly on Ctrl-C or EPIPE
  89. # while treating other exceptions as before.
  90. def std_exceptions(etype, value, tb):
  91. sys.excepthook = sys.__excepthook__
  92. if issubclass(etype, KeyboardInterrupt):
  93. pass
  94. elif issubclass(etype, IOError) and value.errno == errno.EPIPE:
  95. pass
  96. else:
  97. sys.__excepthook__(etype, value, tb)
  98. sys.excepthook = std_exceptions
  99.  
  100. if os.geteuid() != 0:
  101. sys.stderr.write("Sorry, root permission required.\n")
  102. if __name__ == '__main__':
  103. sys.stderr.close()
  104. sys.exit(1)
  105.  
  106. uname = os.uname()
  107. if uname[0] == "FreeBSD":
  108. proc = "/compat/linux/proc/"
  109. else:
  110. proc = "/proc/"
  111.  
  112. split_args = False
  113. if len(sys.argv) == 2 and sys.argv[1] == "--split-args":
  114. split_args = True
  115.  
  116. PAGESIZE = os.sysconf("SC_PAGE_SIZE") / 1024 #KiB
  117. our_pid = os.getpid()
  118.  
  119.  
  120. #(major,minor,release)
  121. def kernel_ver():
  122. kv = open(proc + "sys/kernel/osrelease", "rt").readline().split(".")[:3]
  123. last = len(kv)
  124. if last == 2:
  125. kv.append('0')
  126. last -= 1
  127. for char in "-_":
  128. kv[last] = kv[last].split(char)[0]
  129. try:
  130. int(kv[last])
  131. except:
  132. kv[last] = 0
  133. return (int(kv[0]), int(kv[1]), int(kv[2]))
  134.  
  135. try:
  136. kv = kernel_ver()
  137. except (IOError, OSError):
  138. val = sys.exc_info()[1]
  139. if val.errno == errno.ENOENT:
  140. sys.stderr.write(
  141. "Couldn't access /proc\n"
  142. "Only GNU/Linux and FreeBSD (with linprocfs) are supported\n")
  143. sys.exit(2)
  144. else:
  145. raise
  146.  
  147. have_pss = 0
  148.  
  149.  
  150. #return Private,Shared
  151. #Note shared is always a subset of rss (trs is not always)
  152. def getMemStats(pid):
  153. global have_pss
  154. mem_id = pid #unique
  155. Private_lines = []
  156. Shared_lines = []
  157. Pss_lines = []
  158. Rss = (int(open(proc + str(pid) + "/statm", "rt").readline().split()[1])
  159. * PAGESIZE)
  160. if os.path.exists(proc + str(pid) + "/smaps"): #stat
  161. digester = md5_new()
  162. for line in open(proc + str(pid) + "/smaps", "rb").readlines(): #open
  163. # Note we checksum smaps as maps is usually but
  164. # not always different for separate processes.
  165. digester.update(line)
  166. line = line.decode("ascii")
  167. if line.startswith("Shared"):
  168. Shared_lines.append(line)
  169. elif line.startswith("Private"):
  170. Private_lines.append(line)
  171. elif line.startswith("Pss"):
  172. have_pss = 1
  173. Pss_lines.append(line)
  174. mem_id = digester.hexdigest()
  175. Shared = sum([int(line.split()[1]) for line in Shared_lines])
  176. Private = sum([int(line.split()[1]) for line in Private_lines])
  177. #Note Shared + Private = Rss above
  178. #The Rss in smaps includes video card mem etc.
  179. if have_pss:
  180. pss_adjust = 0.5 # add 0.5KiB as this avg error due to trunctation
  181. Pss = sum([float(line.split()[1])+pss_adjust for line in Pss_lines])
  182. Shared = Pss - Private
  183. elif (2,6,1) <= kv <= (2,6,9):
  184. Shared = 0 #lots of overestimation, but what can we do?
  185. Private = Rss
  186. else:
  187. Shared = int(open(proc+str(pid)+"/statm", "rt").readline().split()[2])
  188. Shared *= PAGESIZE
  189. Private = Rss - Shared
  190. return (Private, Shared, mem_id)
  191.  
  192.  
  193. def getCmdName(pid):
  194. cmdline = open(proc+"%d/cmdline" % pid, "rt").read().split("\0")
  195. if cmdline[-1] == '' and len(cmdline) > 1:
  196. cmdline = cmdline[:-1]
  197. path = os.path.realpath(proc+"%d/exe" % pid) #exception for kernel threads
  198. if split_args:
  199. return " ".join(cmdline)
  200. if path.endswith(" (deleted)"):
  201. path = path[:-10]
  202. if os.path.exists(path):
  203. path += " [updated]"
  204. else:
  205. #The path could be have prelink stuff so try cmdline
  206. #which might have the full path present. This helped for:
  207. #/usr/libexec/notification-area-applet.#prelink#.fX7LCT (deleted)
  208. if os.path.exists(cmdline[0]):
  209. path = cmdline[0] + " [updated]"
  210. else:
  211. path += " [deleted]"
  212. exe = os.path.basename(path)
  213. cmd = open(proc+"%d/status" % pid, "rt").readline()[6:-1]
  214. if exe.startswith(cmd):
  215. cmd = exe #show non truncated version
  216. #Note because we show the non truncated name
  217. #one can have separated programs as follows:
  218. #584.0 KiB + 1.0 MiB = 1.6 MiB mozilla-thunder (exe -> bash)
  219. # 56.0 MiB + 22.2 MiB = 78.2 MiB mozilla-thunderbird-bin
  220. return cmd
  221.  
  222.  
  223. cmds = {}
  224. shareds = {}
  225. mem_ids = {}
  226. count = {}
  227. for pid in os.listdir(proc):
  228. if not pid.isdigit():
  229. continue
  230. pid = int(pid)
  231. if pid == our_pid:
  232. continue
  233. try:
  234. cmd = getCmdName(pid)
  235. except:
  236. #permission denied or
  237. #kernel threads don't have exe links or
  238. #process gone
  239. continue
  240. try:
  241. private, shared, mem_id = getMemStats(pid)
  242. except:
  243. continue #process gone
  244. if shareds.get(cmd):
  245. if have_pss: #add shared portion of PSS together
  246. shareds[cmd] += shared
  247. elif shareds[cmd] < shared: #just take largest shared val
  248. shareds[cmd] = shared
  249. else:
  250. shareds[cmd] = shared
  251. cmds[cmd] = cmds.setdefault(cmd, 0) + private
  252. if cmd in count:
  253. count[cmd] += 1
  254. else:
  255. count[cmd] = 1
  256. mem_ids.setdefault(cmd, {}).update({mem_id:None})
  257.  
  258. #Add shared mem for each program
  259. total = 0
  260. for cmd in cmds:
  261. cmd_count = count[cmd]
  262. if len(mem_ids[cmd]) == 1 and cmd_count > 1:
  263. # Assume this program is using CLONE_VM without CLONE_THREAD
  264. # so only account for one of the processes
  265. cmds[cmd] /= cmd_count
  266. if have_pss:
  267. shareds[cmd] /= cmd_count
  268. cmds[cmd] = cmds[cmd] + shareds[cmd]
  269. total += cmds[cmd] #valid if PSS available
  270.  
  271. if sys.version_info >= (2, 6):
  272. sort_list = sorted(cmds.items(), key=lambda x:x[1])
  273. else:
  274. sort_list = cmds.items()
  275. sort_list.sort(lambda x, y:cmp(x[1], y[1]))
  276. # list wrapping is redundant on <py3k, needed for >=pyk3 however
  277. sort_list = list(filter(lambda x:x[1], sort_list)) #get rid of 0 sized processes
  278.  
  279.  
  280. #The following matches "du -h" output
  281. #see also human.py
  282. def human(num, power="Ki"):
  283. powers = ["Ki", "Mi", "Gi", "Ti"]
  284. while num >= 1000: #4 digits
  285. num /= 1024.0
  286. power = powers[powers.index(power)+1]
  287. return "%.1f %s" % (num, power)
  288.  
  289.  
  290. def cmd_with_count(cmd, count):
  291. if count > 1:
  292. return "%s (%u)" % (cmd, count)
  293. else:
  294. return cmd
  295.  
  296.  
  297. if __name__ == '__main__':
  298. sys.stdout.write(" Private + Shared = RAM used\tProgram \n\n")
  299. for cmd in sort_list:
  300. sys.stdout.write("%8sB + %8sB = %8sB\t%s\n" %
  301. (human(cmd[1]-shareds[cmd[0]]),
  302. human(shareds[cmd[0]]), human(cmd[1]),
  303. cmd_with_count(cmd[0], count[cmd[0]])))
  304. if have_pss:
  305. sys.stdout.write("%s\n%s%8sB\n%s\n" %
  306. ("-" * 33, " " * 24, human(total), "=" * 33))
  307. sys.stdout.write("\n Private + Shared = RAM used\tProgram \n\n")
  308. # We must close explicitly, so that any EPIPE exception
  309. # is handled by our excepthook, rather than the default
  310. # one which is reenabled after this script finishes.
  311. sys.stdout.close()
  312.  
  313.  
  314. #Warn of possible inaccuracies
  315. #2 = accurate & can total
  316. #1 = accurate only considering each process in isolation
  317. #0 = some shared mem not reported
  318. #-1= all shared mem not reported
  319. def shared_val_accuracy():
  320. """http://wiki.apache.org/spamassassin/TopSharedMemoryBug"""
  321. if kv[:2] == (2,4):
  322. if open(proc+"meminfo", "rt").read().find("Inact_") == -1:
  323. return 1
  324. return 0
  325. elif kv[:2] == (2,6):
  326. pid = str(os.getpid())
  327. if os.path.exists(proc+pid+"/smaps"):
  328. if open(proc+pid+"/smaps", "rt").read().find("Pss:")!=-1:
  329. return 2
  330. else:
  331. return 1
  332. if (2,6,1) <= kv <= (2,6,9):
  333. return -1
  334. return 0
  335. elif kv[0] > 2:
  336. return 2
  337. else:
  338. return 1
  339.  
  340.  
  341. if __name__ == '__main__':
  342. vm_accuracy = shared_val_accuracy()
  343. if vm_accuracy == -1:
  344. sys.stderr.write(
  345. "Warning: Shared memory is not reported by this system.\n"
  346. )
  347. sys.stderr.write(
  348. "Values reported will be too large, and totals are not reported\n"
  349. )
  350. elif vm_accuracy == 0:
  351. sys.stderr.write(
  352. "Warning: Shared memory is not reported accurately by this system.\n"
  353. )
  354. sys.stderr.write(
  355. "Values reported could be too large, and totals are not reported\n"
  356. )
  357. elif vm_accuracy == 1:
  358. sys.stderr.write(
  359. "Warning: Shared memory is slightly over-estimated by this system\n"
  360. "for each program, so totals are not reported.\n"
  361. )
  362. sys.stderr.close()
Advertisement
Add Comment
Please, Sign In to add comment