| Index: tools/deep_memory_profiler/lib/subcommand.py
|
| diff --git a/tools/deep_memory_profiler/lib/subcommand.py b/tools/deep_memory_profiler/lib/subcommand.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..25416f6ee39f0fe0effc51c406dea2a4ff8b2977
|
| --- /dev/null
|
| +++ b/tools/deep_memory_profiler/lib/subcommand.py
|
| @@ -0,0 +1,160 @@
|
| +# 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 logging
|
| +import optparse
|
| +import os
|
| +import re
|
| +
|
| +from lib.bucket import BucketSet
|
| +from lib.dump import Dump, DumpList
|
| +from lib.symbol import SymbolDataSources, SymbolMappingCache, SymbolFinder
|
| +from lib.symbol import proc_maps
|
| +from lib.symbol import FUNCTION_SYMBOLS, SOURCEFILE_SYMBOLS, TYPEINFO_SYMBOLS
|
| +
|
| +
|
| +LOGGER = logging.getLogger('dmprof')
|
| +
|
| +BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| +CHROME_SRC_PATH = os.path.join(BASE_PATH, os.pardir, os.pardir)
|
| +
|
| +
|
| +class SubCommand(object):
|
| + """Subclasses are a subcommand for this executable.
|
| +
|
| + See COMMANDS in main() in dmprof.py.
|
| + """
|
| + _DEVICE_BINDIRS = ['/data/data/', '/data/app-lib/', '/data/local/tmp']
|
| +
|
| + def __init__(self, usage):
|
| + self._parser = optparse.OptionParser(usage)
|
| +
|
| + @staticmethod
|
| + def load_basic_files(
|
| + dump_path, multiple, no_dump=False, alternative_dirs=None):
|
| + prefix = SubCommand._find_prefix(dump_path)
|
| + # If the target process is estimated to be working on Android, converts
|
| + # a path in the Android device to a path estimated to be corresponding in
|
| + # the host. Use --alternative-dirs to specify the conversion manually.
|
| + if not alternative_dirs:
|
| + alternative_dirs = SubCommand._estimate_alternative_dirs(prefix)
|
| + if alternative_dirs:
|
| + for device, host in alternative_dirs.iteritems():
|
| + LOGGER.info('Assuming %s on device as %s on host' % (device, host))
|
| + symbol_data_sources = SymbolDataSources(prefix, alternative_dirs)
|
| + symbol_data_sources.prepare()
|
| + bucket_set = BucketSet()
|
| + bucket_set.load(prefix)
|
| + if not no_dump:
|
| + if multiple:
|
| + dump_list = DumpList.load(SubCommand._find_all_dumps(dump_path))
|
| + else:
|
| + dump = Dump.load(dump_path)
|
| + symbol_mapping_cache = SymbolMappingCache()
|
| + with open(prefix + '.cache.function', 'a+') as cache_f:
|
| + symbol_mapping_cache.update(
|
| + FUNCTION_SYMBOLS, bucket_set,
|
| + SymbolFinder(FUNCTION_SYMBOLS, symbol_data_sources), cache_f)
|
| + with open(prefix + '.cache.typeinfo', 'a+') as cache_f:
|
| + symbol_mapping_cache.update(
|
| + TYPEINFO_SYMBOLS, bucket_set,
|
| + SymbolFinder(TYPEINFO_SYMBOLS, symbol_data_sources), cache_f)
|
| + with open(prefix + '.cache.sourcefile', 'a+') as cache_f:
|
| + symbol_mapping_cache.update(
|
| + SOURCEFILE_SYMBOLS, bucket_set,
|
| + SymbolFinder(SOURCEFILE_SYMBOLS, symbol_data_sources), cache_f)
|
| + bucket_set.symbolize(symbol_mapping_cache)
|
| + if no_dump:
|
| + return bucket_set
|
| + elif multiple:
|
| + return (bucket_set, dump_list)
|
| + else:
|
| + return (bucket_set, dump)
|
| +
|
| + @staticmethod
|
| + def _find_prefix(path):
|
| + return re.sub('\.[0-9][0-9][0-9][0-9]\.heap', '', path)
|
| +
|
| + @staticmethod
|
| + def _estimate_alternative_dirs(prefix):
|
| + """Estimates a path in host from a corresponding path in target device.
|
| +
|
| + For Android, dmprof.py should find symbol information from binaries in
|
| + the host instead of the Android device because dmprof.py doesn't run on
|
| + the Android device. This method estimates a path in the host
|
| + corresponding to a path in the Android device.
|
| +
|
| + Returns:
|
| + A dict that maps a path in the Android device to a path in the host.
|
| + If a file in SubCommand._DEVICE_BINDIRS is found in /proc/maps, it
|
| + assumes the process was running on Android and maps the path to
|
| + "out/Debug/lib" in the Chromium directory. An empty dict is returned
|
| + unless Android.
|
| + """
|
| + device_lib_path_candidates = set()
|
| +
|
| + with open(prefix + '.maps') as maps_f:
|
| + maps = proc_maps.ProcMaps.load(maps_f)
|
| + for entry in maps:
|
| + name = entry.as_dict()['name']
|
| + if any([base_dir in name for base_dir in SubCommand._DEVICE_BINDIRS]):
|
| + device_lib_path_candidates.add(os.path.dirname(name))
|
| +
|
| + if len(device_lib_path_candidates) == 1:
|
| + return {device_lib_path_candidates.pop(): os.path.join(
|
| + CHROME_SRC_PATH, 'out', 'Debug', 'lib')}
|
| + else:
|
| + return {}
|
| +
|
| + @staticmethod
|
| + def _find_all_dumps(dump_path):
|
| + prefix = SubCommand._find_prefix(dump_path)
|
| + dump_path_list = [dump_path]
|
| +
|
| + n = int(dump_path[len(dump_path) - 9 : len(dump_path) - 5])
|
| + n += 1
|
| + skipped = 0
|
| + while True:
|
| + p = '%s.%04d.heap' % (prefix, n)
|
| + if os.path.exists(p) and os.stat(p).st_size:
|
| + dump_path_list.append(p)
|
| + else:
|
| + if skipped > 10:
|
| + break
|
| + skipped += 1
|
| + n += 1
|
| +
|
| + return dump_path_list
|
| +
|
| + @staticmethod
|
| + def _find_all_buckets(dump_path):
|
| + prefix = SubCommand._find_prefix(dump_path)
|
| + bucket_path_list = []
|
| +
|
| + n = 0
|
| + while True:
|
| + path = '%s.%04d.buckets' % (prefix, n)
|
| + if not os.path.exists(path):
|
| + if n > 10:
|
| + break
|
| + n += 1
|
| + continue
|
| + bucket_path_list.append(path)
|
| + n += 1
|
| +
|
| + return bucket_path_list
|
| +
|
| + def _parse_args(self, sys_argv, required):
|
| + options, args = self._parser.parse_args(sys_argv)
|
| + if len(args) < required + 1:
|
| + self._parser.error('needs %d argument(s).\n' % required)
|
| + return None
|
| + return (options, args)
|
| +
|
| + @staticmethod
|
| + def _parse_policy_list(options_policy):
|
| + if options_policy:
|
| + return options_policy.split(',')
|
| + else:
|
| + return None
|
|
|