OLD | NEW |
| (Empty) |
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 | |
21 import collections | |
22 import logging | |
23 import os | |
24 import psutil | |
25 import sys | |
26 | |
27 | |
28 if 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 | |
35 class _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 | |
44 def _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 | |
56 def 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 | |
79 def 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 | |
101 def 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 | |
118 def 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 | |
142 if __name__ == '__main__': | |
143 sys.exit(main(sys.argv)) | |
OLD | NEW |