Home | History | Annotate | Download | only in lib
      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.
      4 
      5 import logging
      6 import optparse
      7 import os
      8 import re
      9 
     10 from lib.bucket import BucketSet
     11 from lib.dump import Dump, DumpList
     12 from lib.symbol import SymbolDataSources, SymbolMappingCache, SymbolFinder
     13 from lib.symbol import procfs
     14 from lib.symbol import FUNCTION_SYMBOLS, SOURCEFILE_SYMBOLS, TYPEINFO_SYMBOLS
     15 
     16 
     17 LOGGER = logging.getLogger('dmprof')
     18 
     19 BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
     20 CHROME_SRC_PATH = os.path.join(BASE_PATH, os.pardir, os.pardir)
     21 
     22 
     23 class SubCommand(object):
     24   """Subclasses are a subcommand for this executable.
     25 
     26   See COMMANDS in main() in dmprof.py.
     27   """
     28   _DEVICE_BINDIRS = ['/data/data/', '/data/app-lib/', '/data/local/tmp']
     29 
     30   def __init__(self, usage):
     31     self._parser = optparse.OptionParser(usage)
     32 
     33   @staticmethod
     34   def load_basic_files(
     35       dump_path, multiple, no_dump=False, alternative_dirs=None):
     36     prefix = SubCommand._find_prefix(dump_path)
     37     # If the target process is estimated to be working on Android, converts
     38     # a path in the Android device to a path estimated to be corresponding in
     39     # the host.  Use --alternative-dirs to specify the conversion manually.
     40     if not alternative_dirs:
     41       alternative_dirs = SubCommand._estimate_alternative_dirs(prefix)
     42     if alternative_dirs:
     43       for device, host in alternative_dirs.iteritems():
     44         LOGGER.info('Assuming %s on device as %s on host' % (device, host))
     45     symbol_data_sources = SymbolDataSources(prefix, alternative_dirs)
     46     symbol_data_sources.prepare()
     47     bucket_set = BucketSet()
     48     bucket_set.load(prefix)
     49     if not no_dump:
     50       if multiple:
     51         dump_list = DumpList.load(SubCommand._find_all_dumps(dump_path))
     52       else:
     53         dump = Dump.load(dump_path)
     54     symbol_mapping_cache = SymbolMappingCache()
     55     with open(prefix + '.cache.function', 'a+') as cache_f:
     56       symbol_mapping_cache.update(
     57           FUNCTION_SYMBOLS, bucket_set,
     58           SymbolFinder(FUNCTION_SYMBOLS, symbol_data_sources), cache_f)
     59     with open(prefix + '.cache.typeinfo', 'a+') as cache_f:
     60       symbol_mapping_cache.update(
     61           TYPEINFO_SYMBOLS, bucket_set,
     62           SymbolFinder(TYPEINFO_SYMBOLS, symbol_data_sources), cache_f)
     63     with open(prefix + '.cache.sourcefile', 'a+') as cache_f:
     64       symbol_mapping_cache.update(
     65           SOURCEFILE_SYMBOLS, bucket_set,
     66           SymbolFinder(SOURCEFILE_SYMBOLS, symbol_data_sources), cache_f)
     67     bucket_set.symbolize(symbol_mapping_cache)
     68     if no_dump:
     69       return bucket_set
     70     elif multiple:
     71       return (bucket_set, dump_list)
     72     else:
     73       return (bucket_set, dump)
     74 
     75   @staticmethod
     76   def _find_prefix(path):
     77     return re.sub('\.[0-9][0-9][0-9][0-9]\.heap', '', path)
     78 
     79   @staticmethod
     80   def _estimate_alternative_dirs(prefix):
     81     """Estimates a path in host from a corresponding path in target device.
     82 
     83     For Android, dmprof.py should find symbol information from binaries in
     84     the host instead of the Android device because dmprof.py doesn't run on
     85     the Android device.  This method estimates a path in the host
     86     corresponding to a path in the Android device.
     87 
     88     Returns:
     89         A dict that maps a path in the Android device to a path in the host.
     90         If a file in SubCommand._DEVICE_BINDIRS is found in /proc/maps, it
     91         assumes the process was running on Android and maps the path to
     92         "out/Debug/lib" in the Chromium directory.  An empty dict is returned
     93         unless Android.
     94     """
     95     device_lib_path_candidates = set()
     96 
     97     with open(prefix + '.maps') as maps_f:
     98       maps = procfs.ProcMaps.load_file(maps_f)
     99       for entry in maps:
    100         name = entry.as_dict()['name']
    101         if any([base_dir in name for base_dir in SubCommand._DEVICE_BINDIRS]):
    102           device_lib_path_candidates.add(os.path.dirname(name))
    103 
    104     if len(device_lib_path_candidates) == 1:
    105       return {device_lib_path_candidates.pop(): os.path.join(
    106                   CHROME_SRC_PATH, 'out', 'Debug', 'lib')}
    107     else:
    108       return {}
    109 
    110   @staticmethod
    111   def _find_all_dumps(dump_path):
    112     prefix = SubCommand._find_prefix(dump_path)
    113     dump_path_list = [dump_path]
    114 
    115     n = int(dump_path[len(dump_path) - 9 : len(dump_path) - 5])
    116     n += 1
    117     skipped = 0
    118     while True:
    119       p = '%s.%04d.heap' % (prefix, n)
    120       if os.path.exists(p) and os.stat(p).st_size:
    121         dump_path_list.append(p)
    122       else:
    123         if skipped > 10:
    124           break
    125         skipped += 1
    126       n += 1
    127 
    128     return dump_path_list
    129 
    130   @staticmethod
    131   def _find_all_buckets(dump_path):
    132     prefix = SubCommand._find_prefix(dump_path)
    133     bucket_path_list = []
    134 
    135     n = 0
    136     while True:
    137       path = '%s.%04d.buckets' % (prefix, n)
    138       if not os.path.exists(path):
    139         if n > 10:
    140           break
    141         n += 1
    142         continue
    143       bucket_path_list.append(path)
    144       n += 1
    145 
    146     return bucket_path_list
    147 
    148   def _parse_args(self, sys_argv, required):
    149     options, args = self._parser.parse_args(sys_argv)
    150     if len(args) < required + 1:
    151       self._parser.error('needs %d argument(s).\n' % required)
    152       return None
    153     return (options, args)
    154 
    155   @staticmethod
    156   def _parse_policy_list(options_policy):
    157     if options_policy:
    158       return options_policy.split(',')
    159     else:
    160       return None
    161