summaryrefslogtreecommitdiffstats
path: root/tools/deep_memory_profiler/lib/subcommand.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/deep_memory_profiler/lib/subcommand.py')
-rw-r--r--tools/deep_memory_profiler/lib/subcommand.py160
1 files changed, 160 insertions, 0 deletions
diff --git a/tools/deep_memory_profiler/lib/subcommand.py b/tools/deep_memory_profiler/lib/subcommand.py
new file mode 100644
index 0000000..25416f6
--- /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