Advertisement
Guest User

Untitled

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