#!/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 |r [...] # # If 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. 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))