• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
5import logging
6import optparse
7import os
8import re
9
10from lib.bucket import BucketSet
11from lib.dump import Dump, DumpList
12from lib.symbol import SymbolDataSources, SymbolMappingCache, SymbolFinder
13from lib.symbol import procfs
14from lib.symbol import FUNCTION_SYMBOLS, SOURCEFILE_SYMBOLS, TYPEINFO_SYMBOLS
15
16
17LOGGER = logging.getLogger('dmprof')
18
19BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
20CHROME_SRC_PATH = os.path.join(BASE_PATH, os.pardir, os.pardir)
21
22
23class 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