OLD | NEW |
(Empty) | |
| 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 proc_maps |
| 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 = proc_maps.ProcMaps.load(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 |
OLD | NEW |