| #!/usr/bin/env python | 
 | # Copyright 2013 The Chromium Authors. All rights reserved. | 
 | # Use of this source code is governed by a BSD-style license that can be | 
 | # found in the LICENSE file. | 
 |  | 
 | # Counts a resident set size (RSS) of multiple processes without double-counts. | 
 | # If they share the same page frame, the page frame is counted only once. | 
 | # | 
 | # Usage: | 
 | # ./multi-process-rss.py <pid>|<pid>r [...] | 
 | # | 
 | # If <pid> has 'r' at the end, all descendants of the process are accounted. | 
 | # | 
 | # Example: | 
 | # ./multi-process-rss.py 12345 23456r | 
 | # | 
 | # The command line above counts the RSS of 1) process 12345, 2) process 23456 | 
 | # and 3) all descendant processes of process 23456. | 
 |  | 
 | from __future__ import print_function | 
 |  | 
 | import collections | 
 | import logging | 
 | import os | 
 | import psutil | 
 | import sys | 
 |  | 
 |  | 
 | if sys.platform.startswith('linux'): | 
 |   _TOOLS_PATH = os.path.dirname(os.path.abspath(__file__)) | 
 |   _TOOLS_LINUX_PATH = os.path.join(_TOOLS_PATH, 'linux') | 
 |   sys.path.append(_TOOLS_LINUX_PATH) | 
 |   import procfs  # pylint: disable=F0401 | 
 |  | 
 |  | 
 | class _NullHandler(logging.Handler): | 
 |   def emit(self, record): | 
 |     pass | 
 |  | 
 |  | 
 | _LOGGER = logging.getLogger('multi-process-rss') | 
 | _LOGGER.addHandler(_NullHandler()) | 
 |  | 
 |  | 
 | def _recursive_get_children(pid): | 
 |   try: | 
 |     children = psutil.Process(pid).get_children() | 
 |   except psutil.error.NoSuchProcess: | 
 |     return [] | 
 |   descendant = [] | 
 |   for child in children: | 
 |     descendant.append(child.pid) | 
 |     descendant.extend(_recursive_get_children(child.pid)) | 
 |   return descendant | 
 |  | 
 |  | 
 | def list_pids(argv): | 
 |   pids = [] | 
 |   for arg in argv[1:]: | 
 |     try: | 
 |       if arg.endswith('r'): | 
 |         recursive = True | 
 |         pid = int(arg[:-1]) | 
 |       else: | 
 |         recursive = False | 
 |         pid = int(arg) | 
 |     except ValueError: | 
 |       raise SyntaxError("%s is not an integer." % arg) | 
 |     else: | 
 |       pids.append(pid) | 
 |     if recursive: | 
 |       children = _recursive_get_children(pid) | 
 |       pids.extend(children) | 
 |  | 
 |   pids = sorted(set(pids), key=pids.index)  # uniq: maybe slow, but simple. | 
 |  | 
 |   return pids | 
 |  | 
 |  | 
 | def count_pageframes(pids): | 
 |   pageframes = collections.defaultdict(int) | 
 |   pagemap_dct = {} | 
 |   for pid in pids: | 
 |     maps = procfs.ProcMaps.load(pid) | 
 |     if not maps: | 
 |       _LOGGER.warning('/proc/%d/maps not found.' % pid) | 
 |       continue | 
 |     pagemap = procfs.ProcPagemap.load(pid, maps) | 
 |     if not pagemap: | 
 |       _LOGGER.warning('/proc/%d/pagemap not found.' % pid) | 
 |       continue | 
 |     pagemap_dct[pid] = pagemap | 
 |  | 
 |   for pid, pagemap in pagemap_dct.iteritems(): | 
 |     for vma in pagemap.vma_internals.itervalues(): | 
 |       for pageframe, number in vma.pageframes.iteritems(): | 
 |         pageframes[pageframe] += number | 
 |  | 
 |   return pageframes | 
 |  | 
 |  | 
 | def count_statm(pids): | 
 |   resident = 0 | 
 |   shared = 0 | 
 |   private = 0 | 
 |  | 
 |   for pid in pids: | 
 |     statm = procfs.ProcStatm.load(pid) | 
 |     if not statm: | 
 |       _LOGGER.warning('/proc/%d/statm not found.' % pid) | 
 |       continue | 
 |     resident += statm.resident | 
 |     shared += statm.share | 
 |     private += (statm.resident - statm.share) | 
 |  | 
 |   return (resident, shared, private) | 
 |  | 
 |  | 
 | def main(argv): | 
 |   logging_handler = logging.StreamHandler() | 
 |   logging_handler.setLevel(logging.WARNING) | 
 |   logging_handler.setFormatter(logging.Formatter( | 
 |       '%(asctime)s:%(name)s:%(levelname)s:%(message)s')) | 
 |  | 
 |   _LOGGER.setLevel(logging.WARNING) | 
 |   _LOGGER.addHandler(logging_handler) | 
 |  | 
 |   if sys.platform.startswith('linux'): | 
 |     logging.getLogger('procfs').setLevel(logging.WARNING) | 
 |     logging.getLogger('procfs').addHandler(logging_handler) | 
 |     pids = list_pids(argv) | 
 |     pageframes = count_pageframes(pids) | 
 |   else: | 
 |     _LOGGER.error('%s is not supported.' % sys.platform) | 
 |     return 1 | 
 |  | 
 |   # TODO(dmikurube): Classify this total RSS. | 
 |   print(len(pageframes) * 4096) | 
 |  | 
 |   return 0 | 
 |  | 
 |  | 
 | if __name__ == '__main__': | 
 |   sys.exit(main(sys.argv)) |