summaryrefslogtreecommitdiffstats
path: root/tools/deep_memory_profiler/subcommands/policies.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/deep_memory_profiler/subcommands/policies.py')
-rw-r--r--tools/deep_memory_profiler/subcommands/policies.py375
1 files changed, 375 insertions, 0 deletions
diff --git a/tools/deep_memory_profiler/subcommands/policies.py b/tools/deep_memory_profiler/subcommands/policies.py
new file mode 100644
index 0000000..182959b
--- /dev/null
+++ b/tools/deep_memory_profiler/subcommands/policies.py
@@ -0,0 +1,375 @@
+# 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.
+
+import datetime
+import json
+import logging
+import sys
+
+from lib.bucket import BUCKET_ID, COMMITTED
+from lib.pageframe import PFNCounts
+from lib.policy import PolicySet
+from lib.subcommand import SubCommand
+
+
+LOGGER = logging.getLogger('dmprof')
+
+
+class PolicyCommands(SubCommand):
+ def __init__(self, command):
+ super(PolicyCommands, self).__init__(
+ 'Usage: %%prog %s [-p POLICY] <first-dump> [shared-first-dumps...]' %
+ command)
+ self._parser.add_option('-p', '--policy', type='string', dest='policy',
+ help='profile with POLICY', metavar='POLICY')
+ self._parser.add_option('--alternative-dirs', dest='alternative_dirs',
+ metavar='/path/on/target@/path/on/host[:...]',
+ help='Read files in /path/on/host/ instead of '
+ 'files in /path/on/target/.')
+
+ def _set_up(self, sys_argv):
+ options, args = self._parse_args(sys_argv, 1)
+ dump_path = args[1]
+ shared_first_dump_paths = args[2:]
+ alternative_dirs_dict = {}
+ if options.alternative_dirs:
+ for alternative_dir_pair in options.alternative_dirs.split(':'):
+ target_path, host_path = alternative_dir_pair.split('@', 1)
+ alternative_dirs_dict[target_path] = host_path
+ (bucket_set, dumps) = SubCommand.load_basic_files(
+ dump_path, True, alternative_dirs=alternative_dirs_dict)
+
+ pfn_counts_dict = {}
+ for shared_first_dump_path in shared_first_dump_paths:
+ shared_dumps = SubCommand._find_all_dumps(shared_first_dump_path)
+ for shared_dump in shared_dumps:
+ pfn_counts = PFNCounts.load(shared_dump)
+ if pfn_counts.pid not in pfn_counts_dict:
+ pfn_counts_dict[pfn_counts.pid] = []
+ pfn_counts_dict[pfn_counts.pid].append(pfn_counts)
+
+ policy_set = PolicySet.load(SubCommand._parse_policy_list(options.policy))
+ return policy_set, dumps, pfn_counts_dict, bucket_set
+
+ @staticmethod
+ def _apply_policy(dump, pfn_counts_dict, policy, bucket_set, first_dump_time):
+ """Aggregates the total memory size of each component.
+
+ Iterate through all stacktraces and attribute them to one of the components
+ based on the policy. It is important to apply policy in right order.
+
+ Args:
+ dump: A Dump object.
+ pfn_counts_dict: A dict mapping a pid to a list of PFNCounts.
+ policy: A Policy object.
+ bucket_set: A BucketSet object.
+ first_dump_time: An integer representing time when the first dump is
+ dumped.
+
+ Returns:
+ A dict mapping components and their corresponding sizes.
+ """
+ LOGGER.info(' %s' % dump.path)
+ all_pfn_dict = {}
+ if pfn_counts_dict:
+ LOGGER.info(' shared with...')
+ for pid, pfnset_list in pfn_counts_dict.iteritems():
+ closest_pfnset_index = None
+ closest_pfnset_difference = 1024.0
+ for index, pfnset in enumerate(pfnset_list):
+ time_difference = pfnset.time - dump.time
+ if time_difference >= 3.0:
+ break
+ elif ((time_difference < 0.0 and pfnset.reason != 'Exiting') or
+ (0.0 <= time_difference and time_difference < 3.0)):
+ closest_pfnset_index = index
+ closest_pfnset_difference = time_difference
+ elif time_difference < 0.0 and pfnset.reason == 'Exiting':
+ closest_pfnset_index = None
+ break
+ if closest_pfnset_index:
+ for pfn, count in pfnset_list[closest_pfnset_index].iter_pfn:
+ all_pfn_dict[pfn] = all_pfn_dict.get(pfn, 0) + count
+ LOGGER.info(' %s (time difference = %f)' %
+ (pfnset_list[closest_pfnset_index].path,
+ closest_pfnset_difference))
+ else:
+ LOGGER.info(' (no match with pid:%d)' % pid)
+
+ sizes = dict((c, 0) for c in policy.components)
+
+ PolicyCommands._accumulate_malloc(dump, policy, bucket_set, sizes)
+ verify_global_stats = PolicyCommands._accumulate_maps(
+ dump, all_pfn_dict, policy, bucket_set, sizes)
+
+ # TODO(dmikurube): Remove the verifying code when GLOBAL_STATS is removed.
+ # http://crbug.com/245603.
+ for verify_key, verify_value in verify_global_stats.iteritems():
+ dump_value = dump.global_stat('%s_committed' % verify_key)
+ if dump_value != verify_value:
+ LOGGER.warn('%25s: %12d != %d (%d)' % (
+ verify_key, dump_value, verify_value, dump_value - verify_value))
+
+ sizes['mmap-no-log'] = (
+ dump.global_stat('profiled-mmap_committed') -
+ sizes['mmap-total-log'])
+ sizes['mmap-total-record'] = dump.global_stat('profiled-mmap_committed')
+ sizes['mmap-total-record-vm'] = dump.global_stat('profiled-mmap_virtual')
+
+ sizes['tc-no-log'] = (
+ dump.global_stat('profiled-malloc_committed') -
+ sizes['tc-total-log'])
+ sizes['tc-total-record'] = dump.global_stat('profiled-malloc_committed')
+ sizes['tc-unused'] = (
+ sizes['mmap-tcmalloc'] -
+ dump.global_stat('profiled-malloc_committed'))
+ if sizes['tc-unused'] < 0:
+ LOGGER.warn(' Assuming tc-unused=0 as it is negative: %d (bytes)' %
+ sizes['tc-unused'])
+ sizes['tc-unused'] = 0
+ sizes['tc-total'] = sizes['mmap-tcmalloc']
+
+ # TODO(dmikurube): global_stat will be deprecated.
+ # See http://crbug.com/245603.
+ for key, value in {
+ 'total': 'total_committed',
+ 'filemapped': 'file_committed',
+ 'absent': 'absent_committed',
+ 'file-exec': 'file-exec_committed',
+ 'file-nonexec': 'file-nonexec_committed',
+ 'anonymous': 'anonymous_committed',
+ 'stack': 'stack_committed',
+ 'other': 'other_committed',
+ 'unhooked-absent': 'nonprofiled-absent_committed',
+ 'total-vm': 'total_virtual',
+ 'filemapped-vm': 'file_virtual',
+ 'anonymous-vm': 'anonymous_virtual',
+ 'other-vm': 'other_virtual' }.iteritems():
+ if key in sizes:
+ sizes[key] = dump.global_stat(value)
+
+ if 'mustbezero' in sizes:
+ removed_list = (
+ 'profiled-mmap_committed',
+ 'nonprofiled-absent_committed',
+ 'nonprofiled-anonymous_committed',
+ 'nonprofiled-file-exec_committed',
+ 'nonprofiled-file-nonexec_committed',
+ 'nonprofiled-stack_committed',
+ 'nonprofiled-other_committed')
+ sizes['mustbezero'] = (
+ dump.global_stat('total_committed') -
+ sum(dump.global_stat(removed) for removed in removed_list))
+ if 'total-exclude-profiler' in sizes:
+ sizes['total-exclude-profiler'] = (
+ dump.global_stat('total_committed') -
+ (sizes['mmap-profiler'] + sizes['mmap-type-profiler']))
+ if 'hour' in sizes:
+ sizes['hour'] = (dump.time - first_dump_time) / 60.0 / 60.0
+ if 'minute' in sizes:
+ sizes['minute'] = (dump.time - first_dump_time) / 60.0
+ if 'second' in sizes:
+ sizes['second'] = dump.time - first_dump_time
+
+ return sizes
+
+ @staticmethod
+ def _accumulate_malloc(dump, policy, bucket_set, sizes):
+ for line in dump.iter_stacktrace:
+ words = line.split()
+ bucket = bucket_set.get(int(words[BUCKET_ID]))
+ if not bucket or bucket.allocator_type == 'malloc':
+ component_match = policy.find_malloc(bucket)
+ elif bucket.allocator_type == 'mmap':
+ continue
+ else:
+ assert False
+ sizes[component_match] += int(words[COMMITTED])
+
+ assert not component_match.startswith('mmap-')
+ if component_match.startswith('tc-'):
+ sizes['tc-total-log'] += int(words[COMMITTED])
+ else:
+ sizes['other-total-log'] += int(words[COMMITTED])
+
+ @staticmethod
+ def _accumulate_maps(dump, pfn_dict, policy, bucket_set, sizes):
+ # TODO(dmikurube): Remove the dict when GLOBAL_STATS is removed.
+ # http://crbug.com/245603.
+ global_stats = {
+ 'total': 0,
+ 'file-exec': 0,
+ 'file-nonexec': 0,
+ 'anonymous': 0,
+ 'stack': 0,
+ 'other': 0,
+ 'nonprofiled-file-exec': 0,
+ 'nonprofiled-file-nonexec': 0,
+ 'nonprofiled-anonymous': 0,
+ 'nonprofiled-stack': 0,
+ 'nonprofiled-other': 0,
+ 'profiled-mmap': 0,
+ }
+
+ for key, value in dump.iter_map:
+ # TODO(dmikurube): Remove the subtotal code when GLOBAL_STATS is removed.
+ # It's temporary verification code for transition described in
+ # http://crbug.com/245603.
+ committed = 0
+ if 'committed' in value[1]:
+ committed = value[1]['committed']
+ global_stats['total'] += committed
+ key = 'other'
+ name = value[1]['vma']['name']
+ if name.startswith('/'):
+ if value[1]['vma']['executable'] == 'x':
+ key = 'file-exec'
+ else:
+ key = 'file-nonexec'
+ elif name == '[stack]':
+ key = 'stack'
+ elif name == '':
+ key = 'anonymous'
+ global_stats[key] += committed
+ if value[0] == 'unhooked':
+ global_stats['nonprofiled-' + key] += committed
+ if value[0] == 'hooked':
+ global_stats['profiled-mmap'] += committed
+
+ if value[0] == 'unhooked':
+ if pfn_dict and dump.pageframe_length:
+ for pageframe in value[1]['pageframe']:
+ component_match = policy.find_unhooked(value, pageframe, pfn_dict)
+ sizes[component_match] += pageframe.size
+ else:
+ component_match = policy.find_unhooked(value)
+ sizes[component_match] += int(value[1]['committed'])
+ elif value[0] == 'hooked':
+ if pfn_dict and dump.pageframe_length:
+ for pageframe in value[1]['pageframe']:
+ component_match, _ = policy.find_mmap(
+ value, bucket_set, pageframe, pfn_dict)
+ sizes[component_match] += pageframe.size
+ assert not component_match.startswith('tc-')
+ if component_match.startswith('mmap-'):
+ sizes['mmap-total-log'] += pageframe.size
+ else:
+ sizes['other-total-log'] += pageframe.size
+ else:
+ component_match, _ = policy.find_mmap(value, bucket_set)
+ sizes[component_match] += int(value[1]['committed'])
+ if component_match.startswith('mmap-'):
+ sizes['mmap-total-log'] += int(value[1]['committed'])
+ else:
+ sizes['other-total-log'] += int(value[1]['committed'])
+ else:
+ LOGGER.error('Unrecognized mapping status: %s' % value[0])
+
+ return global_stats
+
+
+class CSVCommand(PolicyCommands):
+ def __init__(self):
+ super(CSVCommand, self).__init__('csv')
+
+ def do(self, sys_argv):
+ policy_set, dumps, pfn_counts_dict, bucket_set = self._set_up(sys_argv)
+ return CSVCommand._output(
+ policy_set, dumps, pfn_counts_dict, bucket_set, sys.stdout)
+
+ @staticmethod
+ def _output(policy_set, dumps, pfn_counts_dict, bucket_set, out):
+ max_components = 0
+ for label in policy_set:
+ max_components = max(max_components, len(policy_set[label].components))
+
+ for label in sorted(policy_set):
+ components = policy_set[label].components
+ if len(policy_set) > 1:
+ out.write('%s%s\n' % (label, ',' * (max_components - 1)))
+ out.write('%s%s\n' % (
+ ','.join(components), ',' * (max_components - len(components))))
+
+ LOGGER.info('Applying a policy %s to...' % label)
+ for dump in dumps:
+ component_sizes = PolicyCommands._apply_policy(
+ dump, pfn_counts_dict, policy_set[label], bucket_set, dumps[0].time)
+ s = []
+ for c in components:
+ if c in ('hour', 'minute', 'second'):
+ s.append('%05.5f' % (component_sizes[c]))
+ else:
+ s.append('%05.5f' % (component_sizes[c] / 1024.0 / 1024.0))
+ out.write('%s%s\n' % (
+ ','.join(s), ',' * (max_components - len(components))))
+
+ bucket_set.clear_component_cache()
+
+ return 0
+
+
+class JSONCommand(PolicyCommands):
+ def __init__(self):
+ super(JSONCommand, self).__init__('json')
+
+ def do(self, sys_argv):
+ policy_set, dumps, pfn_counts_dict, bucket_set = self._set_up(sys_argv)
+ return JSONCommand._output(
+ policy_set, dumps, pfn_counts_dict, bucket_set, sys.stdout)
+
+ @staticmethod
+ def _output(policy_set, dumps, pfn_counts_dict, bucket_set, out):
+ json_base = {
+ 'version': 'JSON_DEEP_2',
+ 'policies': {},
+ }
+
+ for label in sorted(policy_set):
+ json_base['policies'][label] = {
+ 'legends': policy_set[label].components,
+ 'snapshots': [],
+ }
+
+ LOGGER.info('Applying a policy %s to...' % label)
+ for dump in dumps:
+ component_sizes = PolicyCommands._apply_policy(
+ dump, pfn_counts_dict, policy_set[label], bucket_set, dumps[0].time)
+ component_sizes['dump_path'] = dump.path
+ component_sizes['dump_time'] = datetime.datetime.fromtimestamp(
+ dump.time).strftime('%Y-%m-%d %H:%M:%S')
+ json_base['policies'][label]['snapshots'].append(component_sizes)
+
+ bucket_set.clear_component_cache()
+
+ json.dump(json_base, out, indent=2, sort_keys=True)
+
+ return 0
+
+
+class ListCommand(PolicyCommands):
+ def __init__(self):
+ super(ListCommand, self).__init__('list')
+
+ def do(self, sys_argv):
+ policy_set, dumps, pfn_counts_dict, bucket_set = self._set_up(sys_argv)
+ return ListCommand._output(
+ policy_set, dumps, pfn_counts_dict, bucket_set, sys.stdout)
+
+ @staticmethod
+ def _output(policy_set, dumps, pfn_counts_dict, bucket_set, out):
+ for label in sorted(policy_set):
+ LOGGER.info('Applying a policy %s to...' % label)
+ for dump in dumps:
+ component_sizes = PolicyCommands._apply_policy(
+ dump, pfn_counts_dict, policy_set[label], bucket_set, dump.time)
+ out.write('%s for %s:\n' % (label, dump.path))
+ for c in policy_set[label].components:
+ if c in ['hour', 'minute', 'second']:
+ out.write('%40s %12.3f\n' % (c, component_sizes[c]))
+ else:
+ out.write('%40s %12d\n' % (c, component_sizes[c]))
+
+ bucket_set.clear_component_cache()
+
+ return 0