memorystats 16 KB

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