Fix import error in mac_platform_backend.py
[chromium-blink-merge.git] / tools / deep_memory_profiler / lib / subcommand.py
blob39241092afaa2945aa0513d12c6b47d3bb68b635
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.
5 import logging
6 import optparse
7 import os
8 import re
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
17 LOGGER = logging.getLogger('dmprof')
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)
23 class SubCommand(object):
24 """Subclasses are a subcommand for this executable.
26 See COMMANDS in main() in dmprof.py.
27 """
28 _DEVICE_BINDIRS = ['/data/data/', '/data/app-lib/', '/data/local/tmp']
30 def __init__(self, usage):
31 self._parser = optparse.OptionParser(usage)
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)
75 @staticmethod
76 def _find_prefix(path):
77 return re.sub('\.[0-9][0-9][0-9][0-9]\.heap', '', path)
79 @staticmethod
80 def _estimate_alternative_dirs(prefix):
81 """Estimates a path in host from a corresponding path in target device.
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.
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()
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))
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 {}
110 @staticmethod
111 def _find_all_dumps(dump_path):
112 prefix = SubCommand._find_prefix(dump_path)
113 dump_path_list = [dump_path]
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
128 return dump_path_list
130 @staticmethod
131 def _find_all_buckets(dump_path):
132 prefix = SubCommand._find_prefix(dump_path)
133 bucket_path_list = []
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
146 return bucket_path_list
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)
155 @staticmethod
156 def _parse_policy_list(options_policy):
157 if options_policy:
158 return options_policy.split(',')
159 else:
160 return None