17dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch# Copyright 2013 The Chromium Authors. All rights reserved.
27dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch# Use of this source code is governed by a BSD-style license that can be
37dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch# found in the LICENSE file.
47dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
57dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdochimport logging
67dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdochimport optparse
77dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdochimport os
87dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdochimport re
97dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
107dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdochfrom lib.bucket import BucketSet
117dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdochfrom lib.dump import Dump, DumpList
127dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdochfrom lib.symbol import SymbolDataSources, SymbolMappingCache, SymbolFinder
13a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)from lib.symbol import procfs
147dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdochfrom lib.symbol import FUNCTION_SYMBOLS, SOURCEFILE_SYMBOLS, TYPEINFO_SYMBOLS
157dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
167dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
177dbb3d5cf0c15f500944d211057644d6a2f37371Ben MurdochLOGGER = logging.getLogger('dmprof')
187dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
197dbb3d5cf0c15f500944d211057644d6a2f37371Ben MurdochBASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
207dbb3d5cf0c15f500944d211057644d6a2f37371Ben MurdochCHROME_SRC_PATH = os.path.join(BASE_PATH, os.pardir, os.pardir)
217dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
227dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
237dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdochclass SubCommand(object):
247dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  """Subclasses are a subcommand for this executable.
257dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
267dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  See COMMANDS in main() in dmprof.py.
277dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  """
287dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  _DEVICE_BINDIRS = ['/data/data/', '/data/app-lib/', '/data/local/tmp']
297dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
307dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  def __init__(self, usage):
317dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    self._parser = optparse.OptionParser(usage)
327dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
337dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  @staticmethod
346e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  def load_basic_files(dump_path, multiple, alternative_dirs=None):
357dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    prefix = SubCommand._find_prefix(dump_path)
367dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    # If the target process is estimated to be working on Android, converts
377dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    # a path in the Android device to a path estimated to be corresponding in
387dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    # the host.  Use --alternative-dirs to specify the conversion manually.
397dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    if not alternative_dirs:
407dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      alternative_dirs = SubCommand._estimate_alternative_dirs(prefix)
417dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    if alternative_dirs:
427dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      for device, host in alternative_dirs.iteritems():
437dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch        LOGGER.info('Assuming %s on device as %s on host' % (device, host))
447dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    symbol_data_sources = SymbolDataSources(prefix, alternative_dirs)
457dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    symbol_data_sources.prepare()
467dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    bucket_set = BucketSet()
477dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    bucket_set.load(prefix)
486e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    if multiple:
496e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      dump_list = DumpList.load(SubCommand._find_all_dumps(dump_path))
506e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    else:
516e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      dump = Dump.load(dump_path)
527dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    symbol_mapping_cache = SymbolMappingCache()
537dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    with open(prefix + '.cache.function', 'a+') as cache_f:
547dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      symbol_mapping_cache.update(
557dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch          FUNCTION_SYMBOLS, bucket_set,
567dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch          SymbolFinder(FUNCTION_SYMBOLS, symbol_data_sources), cache_f)
577dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    with open(prefix + '.cache.typeinfo', 'a+') as cache_f:
587dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      symbol_mapping_cache.update(
597dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch          TYPEINFO_SYMBOLS, bucket_set,
607dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch          SymbolFinder(TYPEINFO_SYMBOLS, symbol_data_sources), cache_f)
617dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    with open(prefix + '.cache.sourcefile', 'a+') as cache_f:
627dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      symbol_mapping_cache.update(
637dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch          SOURCEFILE_SYMBOLS, bucket_set,
647dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch          SymbolFinder(SOURCEFILE_SYMBOLS, symbol_data_sources), cache_f)
657dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    bucket_set.symbolize(symbol_mapping_cache)
666e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    if multiple:
677dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      return (bucket_set, dump_list)
687dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    else:
697dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      return (bucket_set, dump)
707dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
717dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  @staticmethod
727dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  def _find_prefix(path):
737dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    return re.sub('\.[0-9][0-9][0-9][0-9]\.heap', '', path)
747dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
757dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  @staticmethod
767dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  def _estimate_alternative_dirs(prefix):
777dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    """Estimates a path in host from a corresponding path in target device.
787dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
797dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    For Android, dmprof.py should find symbol information from binaries in
807dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    the host instead of the Android device because dmprof.py doesn't run on
817dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    the Android device.  This method estimates a path in the host
827dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    corresponding to a path in the Android device.
837dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
847dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    Returns:
857dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch        A dict that maps a path in the Android device to a path in the host.
867dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch        If a file in SubCommand._DEVICE_BINDIRS is found in /proc/maps, it
877dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch        assumes the process was running on Android and maps the path to
887dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch        "out/Debug/lib" in the Chromium directory.  An empty dict is returned
897dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch        unless Android.
907dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    """
917dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    device_lib_path_candidates = set()
927dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
937dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    with open(prefix + '.maps') as maps_f:
94a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)      maps = procfs.ProcMaps.load_file(maps_f)
957dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      for entry in maps:
967dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch        name = entry.as_dict()['name']
977dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch        if any([base_dir in name for base_dir in SubCommand._DEVICE_BINDIRS]):
987dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch          device_lib_path_candidates.add(os.path.dirname(name))
997dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
1007dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    if len(device_lib_path_candidates) == 1:
1017dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      return {device_lib_path_candidates.pop(): os.path.join(
1027dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch                  CHROME_SRC_PATH, 'out', 'Debug', 'lib')}
1037dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    else:
1047dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      return {}
1057dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
1067dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  @staticmethod
1077dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  def _find_all_dumps(dump_path):
1087dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    prefix = SubCommand._find_prefix(dump_path)
1097dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    dump_path_list = [dump_path]
1107dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
1117dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    n = int(dump_path[len(dump_path) - 9 : len(dump_path) - 5])
1127dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    n += 1
1137dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    skipped = 0
1147dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    while True:
1157dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      p = '%s.%04d.heap' % (prefix, n)
1167dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      if os.path.exists(p) and os.stat(p).st_size:
1177dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch        dump_path_list.append(p)
1187dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      else:
1197dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch        if skipped > 10:
1207dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch          break
1217dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch        skipped += 1
1227dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      n += 1
1237dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
1247dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    return dump_path_list
1257dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
1267dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  @staticmethod
1277dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  def _find_all_buckets(dump_path):
1287dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    prefix = SubCommand._find_prefix(dump_path)
1297dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    bucket_path_list = []
1307dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
1317dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    n = 0
1327dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    while True:
1337dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      path = '%s.%04d.buckets' % (prefix, n)
1347dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      if not os.path.exists(path):
1357dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch        if n > 10:
1367dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch          break
1377dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch        n += 1
1387dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch        continue
1397dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      bucket_path_list.append(path)
1407dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      n += 1
1417dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
1427dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    return bucket_path_list
1437dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
1447dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  def _parse_args(self, sys_argv, required):
1457dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    options, args = self._parser.parse_args(sys_argv)
1467dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    if len(args) < required + 1:
1477dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      self._parser.error('needs %d argument(s).\n' % required)
1487dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      return None
1497dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    return (options, args)
1507dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
1517dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  @staticmethod
1527dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  def _parse_policy_list(options_policy):
1537dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    if options_policy:
1547dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      return options_policy.split(',')
1557dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    else:
1567dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      return None
157