1#!/usr/bin/env python 2# Copyright 2013 The Chromium Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6# Counts a resident set size (RSS) of multiple processes without double-counts. 7# If they share the same page frame, the page frame is counted only once. 8# 9# Usage: 10# ./multi-process-rss.py <pid>|<pid>r [...] 11# 12# If <pid> has 'r' at the end, all descendants of the process are accounted. 13# 14# Example: 15# ./multi-process-rss.py 12345 23456r 16# 17# The command line above counts the RSS of 1) process 12345, 2) process 23456 18# and 3) all descendant processes of process 23456. 19 20 21import collections 22import logging 23import os 24import psutil 25import sys 26 27 28if sys.platform.startswith('linux'): 29 _TOOLS_PATH = os.path.dirname(os.path.abspath(__file__)) 30 _TOOLS_LINUX_PATH = os.path.join(_TOOLS_PATH, 'linux') 31 sys.path.append(_TOOLS_LINUX_PATH) 32 import procfs # pylint: disable=F0401 33 34 35class _NullHandler(logging.Handler): 36 def emit(self, record): 37 pass 38 39 40_LOGGER = logging.getLogger('multi-process-rss') 41_LOGGER.addHandler(_NullHandler()) 42 43 44def _recursive_get_children(pid): 45 try: 46 children = psutil.Process(pid).get_children() 47 except psutil.error.NoSuchProcess: 48 return [] 49 descendant = [] 50 for child in children: 51 descendant.append(child.pid) 52 descendant.extend(_recursive_get_children(child.pid)) 53 return descendant 54 55 56def list_pids(argv): 57 pids = [] 58 for arg in argv[1:]: 59 try: 60 if arg.endswith('r'): 61 recursive = True 62 pid = int(arg[:-1]) 63 else: 64 recursive = False 65 pid = int(arg) 66 except ValueError: 67 raise SyntaxError("%s is not an integer." % arg) 68 else: 69 pids.append(pid) 70 if recursive: 71 children = _recursive_get_children(pid) 72 pids.extend(children) 73 74 pids = sorted(set(pids), key=pids.index) # uniq: maybe slow, but simple. 75 76 return pids 77 78 79def count_pageframes(pids): 80 pageframes = collections.defaultdict(int) 81 pagemap_dct = {} 82 for pid in pids: 83 maps = procfs.ProcMaps.load(pid) 84 if not maps: 85 _LOGGER.warning('/proc/%d/maps not found.' % pid) 86 continue 87 pagemap = procfs.ProcPagemap.load(pid, maps) 88 if not pagemap: 89 _LOGGER.warning('/proc/%d/pagemap not found.' % pid) 90 continue 91 pagemap_dct[pid] = pagemap 92 93 for pid, pagemap in pagemap_dct.iteritems(): 94 for vma in pagemap.vma_internals.itervalues(): 95 for pageframe, number in vma.pageframes.iteritems(): 96 pageframes[pageframe] += number 97 98 return pageframes 99 100 101def count_statm(pids): 102 resident = 0 103 shared = 0 104 private = 0 105 106 for pid in pids: 107 statm = procfs.ProcStatm.load(pid) 108 if not statm: 109 _LOGGER.warning('/proc/%d/statm not found.' % pid) 110 continue 111 resident += statm.resident 112 shared += statm.share 113 private += (statm.resident - statm.share) 114 115 return (resident, shared, private) 116 117 118def main(argv): 119 logging_handler = logging.StreamHandler() 120 logging_handler.setLevel(logging.WARNING) 121 logging_handler.setFormatter(logging.Formatter( 122 '%(asctime)s:%(name)s:%(levelname)s:%(message)s')) 123 124 _LOGGER.setLevel(logging.WARNING) 125 _LOGGER.addHandler(logging_handler) 126 127 if sys.platform.startswith('linux'): 128 logging.getLogger('procfs').setLevel(logging.WARNING) 129 logging.getLogger('procfs').addHandler(logging_handler) 130 pids = list_pids(argv) 131 pageframes = count_pageframes(pids) 132 else: 133 _LOGGER.error('%s is not supported.' % sys.platform) 134 return 1 135 136 # TODO(dmikurube): Classify this total RSS. 137 print len(pageframes) * 4096 138 139 return 0 140 141 142if __name__ == '__main__': 143 sys.exit(main(sys.argv)) 144