• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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