Revert "Always activate MojoApplicationHost"
[chromium-blink-merge.git] / tools / deep_memory_profiler / subcommands / policies.py
blobef34857dfdc55b8d6a88730cbc6a85b1fe71c79c
1 # Copyright 2013 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
5 import datetime
6 import json
7 import logging
8 import sys
10 from lib.pageframe import PFNCounts
11 from lib.policy import PolicySet
12 from lib.subcommand import SubCommand
15 LOGGER = logging.getLogger('dmprof')
18 class PolicyCommands(SubCommand):
19 def __init__(self, command):
20 super(PolicyCommands, self).__init__(
21 'Usage: %%prog %s [-p POLICY] <first-dump> [shared-first-dumps...]' %
22 command)
23 self._parser.add_option('-p', '--policy', type='string', dest='policy',
24 help='profile with POLICY', metavar='POLICY')
25 self._parser.add_option('--alternative-dirs', dest='alternative_dirs',
26 metavar='/path/on/target@/path/on/host[:...]',
27 help='Read files in /path/on/host/ instead of '
28 'files in /path/on/target/.')
29 self._parser.add_option('--timestamp', dest='timestamp',
30 action='store_true', help='Use timestamp.')
31 self._timestamp = False
33 def _set_up(self, sys_argv):
34 options, args = self._parse_args(sys_argv, 1)
35 dump_path = args[1]
36 shared_first_dump_paths = args[2:]
37 alternative_dirs_dict = {}
38 if options.alternative_dirs:
39 for alternative_dir_pair in options.alternative_dirs.split(':'):
40 target_path, host_path = alternative_dir_pair.split('@', 1)
41 alternative_dirs_dict[target_path] = host_path
42 (bucket_set, dumps) = SubCommand.load_basic_files(
43 dump_path, True, alternative_dirs=alternative_dirs_dict)
45 self._timestamp = options.timestamp
47 pfn_counts_dict = {}
48 for shared_first_dump_path in shared_first_dump_paths:
49 shared_dumps = SubCommand._find_all_dumps(shared_first_dump_path)
50 for shared_dump in shared_dumps:
51 pfn_counts = PFNCounts.load(shared_dump)
52 if pfn_counts.pid not in pfn_counts_dict:
53 pfn_counts_dict[pfn_counts.pid] = []
54 pfn_counts_dict[pfn_counts.pid].append(pfn_counts)
56 policy_set = PolicySet.load(SubCommand._parse_policy_list(options.policy))
57 return policy_set, dumps, pfn_counts_dict, bucket_set
59 def _apply_policy(self, dump, pfn_counts_dict, policy, bucket_set,
60 first_dump_time):
61 """Aggregates the total memory size of each component.
63 Iterate through all stacktraces and attribute them to one of the components
64 based on the policy. It is important to apply policy in right order.
66 Args:
67 dump: A Dump object.
68 pfn_counts_dict: A dict mapping a pid to a list of PFNCounts.
69 policy: A Policy object.
70 bucket_set: A BucketSet object.
71 first_dump_time: An integer representing time when the first dump is
72 dumped.
74 Returns:
75 A dict mapping components and their corresponding sizes.
76 """
77 LOGGER.info(' %s' % dump.path)
78 all_pfn_dict = {}
79 if pfn_counts_dict:
80 LOGGER.info(' shared with...')
81 for pid, pfnset_list in pfn_counts_dict.iteritems():
82 closest_pfnset_index = None
83 closest_pfnset_difference = 1024.0
84 for index, pfnset in enumerate(pfnset_list):
85 time_difference = pfnset.time - dump.time
86 if time_difference >= 3.0:
87 break
88 elif ((time_difference < 0.0 and pfnset.reason != 'Exiting') or
89 (0.0 <= time_difference and time_difference < 3.0)):
90 closest_pfnset_index = index
91 closest_pfnset_difference = time_difference
92 elif time_difference < 0.0 and pfnset.reason == 'Exiting':
93 closest_pfnset_index = None
94 break
95 if closest_pfnset_index:
96 for pfn, count in pfnset_list[closest_pfnset_index].iter_pfn:
97 all_pfn_dict[pfn] = all_pfn_dict.get(pfn, 0) + count
98 LOGGER.info(' %s (time difference = %f)' %
99 (pfnset_list[closest_pfnset_index].path,
100 closest_pfnset_difference))
101 else:
102 LOGGER.info(' (no match with pid:%d)' % pid)
104 sizes = dict((c, 0) for c in policy.components)
106 PolicyCommands._accumulate_malloc(dump, policy, bucket_set, sizes)
107 verify_global_stats = PolicyCommands._accumulate_maps(
108 dump, all_pfn_dict, policy, bucket_set, sizes)
110 # TODO(dmikurube): Remove the verifying code when GLOBAL_STATS is removed.
111 # http://crbug.com/245603.
112 for verify_key, verify_value in verify_global_stats.iteritems():
113 dump_value = dump.global_stat('%s_committed' % verify_key)
114 if dump_value != verify_value:
115 LOGGER.warn('%25s: %12d != %d (%d)' % (
116 verify_key, dump_value, verify_value, dump_value - verify_value))
118 sizes['mmap-no-log'] = (
119 dump.global_stat('profiled-mmap_committed') -
120 sizes['mmap-total-log'])
121 sizes['mmap-total-record'] = dump.global_stat('profiled-mmap_committed')
122 sizes['mmap-total-record-vm'] = dump.global_stat('profiled-mmap_virtual')
124 sizes['tc-no-log'] = (
125 dump.global_stat('profiled-malloc_committed') -
126 sizes['tc-total-log'])
127 sizes['tc-total-record'] = dump.global_stat('profiled-malloc_committed')
128 sizes['tc-unused'] = (
129 sizes['mmap-tcmalloc'] -
130 dump.global_stat('profiled-malloc_committed'))
131 if sizes['tc-unused'] < 0:
132 LOGGER.warn(' Assuming tc-unused=0 as it is negative: %d (bytes)' %
133 sizes['tc-unused'])
134 sizes['tc-unused'] = 0
135 sizes['tc-total'] = sizes['mmap-tcmalloc']
137 # TODO(dmikurube): global_stat will be deprecated.
138 # See http://crbug.com/245603.
139 for key, value in {
140 'total': 'total_committed',
141 'filemapped': 'file_committed',
142 'absent': 'absent_committed',
143 'file-exec': 'file-exec_committed',
144 'file-nonexec': 'file-nonexec_committed',
145 'anonymous': 'anonymous_committed',
146 'stack': 'stack_committed',
147 'other': 'other_committed',
148 'unhooked-absent': 'nonprofiled-absent_committed',
149 'total-vm': 'total_virtual',
150 'filemapped-vm': 'file_virtual',
151 'anonymous-vm': 'anonymous_virtual',
152 'other-vm': 'other_virtual' }.iteritems():
153 if key in sizes:
154 sizes[key] = dump.global_stat(value)
156 if 'mustbezero' in sizes:
157 removed_list = (
158 'profiled-mmap_committed',
159 'nonprofiled-absent_committed',
160 'nonprofiled-anonymous_committed',
161 'nonprofiled-file-exec_committed',
162 'nonprofiled-file-nonexec_committed',
163 'nonprofiled-stack_committed',
164 'nonprofiled-other_committed')
165 sizes['mustbezero'] = (
166 dump.global_stat('total_committed') -
167 sum(dump.global_stat(removed) for removed in removed_list))
168 if 'total-exclude-profiler' in sizes:
169 sizes['total-exclude-profiler'] = (
170 dump.global_stat('total_committed') -
171 (sizes['mmap-profiler'] + sizes['mmap-type-profiler']))
172 if 'hour' in sizes:
173 sizes['hour'] = (dump.time - first_dump_time) / 60.0 / 60.0
174 if 'minute' in sizes:
175 sizes['minute'] = (dump.time - first_dump_time) / 60.0
176 if 'second' in sizes:
177 if self._timestamp:
178 sizes['second'] = datetime.datetime.fromtimestamp(dump.time).isoformat()
179 else:
180 sizes['second'] = dump.time - first_dump_time
182 return sizes
184 @staticmethod
185 def _accumulate_malloc(dump, policy, bucket_set, sizes):
186 for bucket_id, _, committed, _, _ in dump.iter_stacktrace:
187 bucket = bucket_set.get(bucket_id)
188 if not bucket or bucket.allocator_type == 'malloc':
189 component_match = policy.find_malloc(bucket)
190 elif bucket.allocator_type == 'mmap':
191 continue
192 else:
193 assert False
194 sizes[component_match] += committed
196 assert not component_match.startswith('mmap-')
197 if component_match.startswith('tc-'):
198 sizes['tc-total-log'] += committed
199 else:
200 sizes['other-total-log'] += committed
202 @staticmethod
203 def _accumulate_maps(dump, pfn_dict, policy, bucket_set, sizes):
204 # TODO(dmikurube): Remove the dict when GLOBAL_STATS is removed.
205 # http://crbug.com/245603.
206 global_stats = {
207 'total': 0,
208 'file-exec': 0,
209 'file-nonexec': 0,
210 'anonymous': 0,
211 'stack': 0,
212 'other': 0,
213 'nonprofiled-file-exec': 0,
214 'nonprofiled-file-nonexec': 0,
215 'nonprofiled-anonymous': 0,
216 'nonprofiled-stack': 0,
217 'nonprofiled-other': 0,
218 'profiled-mmap': 0,
221 for key, value in dump.iter_map:
222 # TODO(dmikurube): Remove the subtotal code when GLOBAL_STATS is removed.
223 # It's temporary verification code for transition described in
224 # http://crbug.com/245603.
225 committed = 0
226 if 'committed' in value[1]:
227 committed = value[1]['committed']
228 global_stats['total'] += committed
229 key = 'other'
230 name = value[1]['vma']['name']
231 if name.startswith('/'):
232 if value[1]['vma']['executable'] == 'x':
233 key = 'file-exec'
234 else:
235 key = 'file-nonexec'
236 elif name == '[stack]':
237 key = 'stack'
238 elif name == '':
239 key = 'anonymous'
240 global_stats[key] += committed
241 if value[0] == 'unhooked':
242 global_stats['nonprofiled-' + key] += committed
243 if value[0] == 'hooked':
244 global_stats['profiled-mmap'] += committed
246 if value[0] == 'unhooked':
247 if pfn_dict and dump.pageframe_length:
248 for pageframe in value[1]['pageframe']:
249 component_match = policy.find_unhooked(value, pageframe, pfn_dict)
250 sizes[component_match] += pageframe.size
251 else:
252 component_match = policy.find_unhooked(value)
253 sizes[component_match] += int(value[1]['committed'])
254 elif value[0] == 'hooked':
255 if pfn_dict and dump.pageframe_length:
256 for pageframe in value[1]['pageframe']:
257 component_match, _ = policy.find_mmap(
258 value, bucket_set, pageframe, pfn_dict)
259 sizes[component_match] += pageframe.size
260 assert not component_match.startswith('tc-')
261 if component_match.startswith('mmap-'):
262 sizes['mmap-total-log'] += pageframe.size
263 else:
264 sizes['other-total-log'] += pageframe.size
265 else:
266 component_match, _ = policy.find_mmap(value, bucket_set)
267 sizes[component_match] += int(value[1]['committed'])
268 if component_match.startswith('mmap-'):
269 sizes['mmap-total-log'] += int(value[1]['committed'])
270 else:
271 sizes['other-total-log'] += int(value[1]['committed'])
272 else:
273 LOGGER.error('Unrecognized mapping status: %s' % value[0])
275 return global_stats
278 class CSVCommand(PolicyCommands):
279 def __init__(self):
280 super(CSVCommand, self).__init__('csv')
282 def do(self, sys_argv):
283 policy_set, dumps, pfn_counts_dict, bucket_set = self._set_up(sys_argv)
284 return self._output(
285 policy_set, dumps, pfn_counts_dict, bucket_set, sys.stdout)
287 def _output(self, policy_set, dumps, pfn_counts_dict, bucket_set, out):
288 max_components = 0
289 for label in policy_set:
290 max_components = max(max_components, len(policy_set[label].components))
292 for label in sorted(policy_set):
293 components = policy_set[label].components
294 if len(policy_set) > 1:
295 out.write('%s%s\n' % (label, ',' * (max_components - 1)))
296 out.write('%s%s\n' % (
297 ','.join(components), ',' * (max_components - len(components))))
299 LOGGER.info('Applying a policy %s to...' % label)
300 for index, dump in enumerate(dumps):
301 if index == 0:
302 first_dump_time = dump.time
303 component_sizes = self._apply_policy(
304 dump, pfn_counts_dict, policy_set[label], bucket_set,
305 first_dump_time)
306 s = []
307 for c in components:
308 if c in ('hour', 'minute', 'second'):
309 if isinstance(component_sizes[c], str):
310 s.append('%s' % component_sizes[c])
311 else:
312 s.append('%05.5f' % (component_sizes[c]))
313 else:
314 s.append('%05.5f' % (component_sizes[c] / 1024.0 / 1024.0))
315 out.write('%s%s\n' % (
316 ','.join(s), ',' * (max_components - len(components))))
318 bucket_set.clear_component_cache()
320 return 0
323 class JSONCommand(PolicyCommands):
324 def __init__(self):
325 super(JSONCommand, self).__init__('json')
327 def do(self, sys_argv):
328 policy_set, dumps, pfn_counts_dict, bucket_set = self._set_up(sys_argv)
329 return self._output(
330 policy_set, dumps, pfn_counts_dict, bucket_set, sys.stdout)
332 def _output(self, policy_set, dumps, pfn_counts_dict, bucket_set, out):
333 json_base = {
334 'version': 'JSON_DEEP_2',
335 'policies': {},
338 for label in sorted(policy_set):
339 json_base['policies'][label] = {
340 'legends': policy_set[label].components,
341 'snapshots': [],
344 LOGGER.info('Applying a policy %s to...' % label)
345 for index, dump in enumerate(dumps):
346 if index == 0:
347 first_dump_time = dump.time
348 component_sizes = self._apply_policy(
349 dump, pfn_counts_dict, policy_set[label], bucket_set,
350 first_dump_time)
351 component_sizes['dump_path'] = dump.path
352 component_sizes['dump_time'] = datetime.datetime.fromtimestamp(
353 dump.time).strftime('%Y-%m-%d %H:%M:%S')
354 json_base['policies'][label]['snapshots'].append(component_sizes)
356 bucket_set.clear_component_cache()
358 json.dump(json_base, out, indent=2, sort_keys=True)
360 return 0
363 class ListCommand(PolicyCommands):
364 def __init__(self):
365 super(ListCommand, self).__init__('list')
367 def do(self, sys_argv):
368 policy_set, dumps, pfn_counts_dict, bucket_set = self._set_up(sys_argv)
369 return self._output(
370 policy_set, dumps, pfn_counts_dict, bucket_set, sys.stdout)
372 def _output(self, policy_set, dumps, pfn_counts_dict, bucket_set, out):
373 for label in sorted(policy_set):
374 LOGGER.info('Applying a policy %s to...' % label)
375 for dump in dumps:
376 component_sizes = self._apply_policy(
377 dump, pfn_counts_dict, policy_set[label], bucket_set, dump.time)
378 out.write('%s for %s:\n' % (label, dump.path))
379 for c in policy_set[label].components:
380 if c in ['hour', 'minute', 'second']:
381 out.write('%40s %12.3f\n' % (c, component_sizes[c]))
382 else:
383 out.write('%40s %12d\n' % (c, component_sizes[c]))
385 bucket_set.clear_component_cache()
387 return 0