OLD | NEW |
---|---|
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 """The deep heap profiler script for Chrome.""" | 5 """The deep heap profiler script for Chrome.""" |
6 | 6 |
7 import copy | 7 import copy |
8 import datetime | 8 import datetime |
9 import json | 9 import json |
10 import logging | 10 import logging |
(...skipping 23 matching lines...) Expand all Loading... | |
34 | 34 |
35 BUCKET_ID = 5 | 35 BUCKET_ID = 5 |
36 VIRTUAL = 0 | 36 VIRTUAL = 0 |
37 COMMITTED = 1 | 37 COMMITTED = 1 |
38 ALLOC_COUNT = 2 | 38 ALLOC_COUNT = 2 |
39 FREE_COUNT = 3 | 39 FREE_COUNT = 3 |
40 NULL_REGEX = re.compile('') | 40 NULL_REGEX = re.compile('') |
41 | 41 |
42 LOGGER = logging.getLogger('dmprof') | 42 LOGGER = logging.getLogger('dmprof') |
43 POLICIES_JSON_PATH = os.path.join(BASE_PATH, 'policies.json') | 43 POLICIES_JSON_PATH = os.path.join(BASE_PATH, 'policies.json') |
44 CHROME_SRC_PATH = os.path.join(BASE_PATH, os.pardir, os.pardir) | |
44 | 45 |
45 | 46 |
46 # Heap Profile Dump versions | 47 # Heap Profile Dump versions |
47 | 48 |
48 # DUMP_DEEP_[1-4] are obsolete. | 49 # DUMP_DEEP_[1-4] are obsolete. |
49 # DUMP_DEEP_2+ distinct mmap regions and malloc chunks. | 50 # DUMP_DEEP_2+ distinct mmap regions and malloc chunks. |
50 # DUMP_DEEP_3+ don't include allocation functions in their stack dumps. | 51 # DUMP_DEEP_3+ don't include allocation functions in their stack dumps. |
51 # DUMP_DEEP_4+ support comments with '#' and global stats "nonprofiled-*". | 52 # DUMP_DEEP_4+ support comments with '#' and global stats "nonprofiled-*". |
52 # DUMP_DEEP_[1-2] should be processed by POLICY_DEEP_1. | 53 # DUMP_DEEP_[1-2] should be processed by POLICY_DEEP_1. |
53 # DUMP_DEEP_[3-4] should be processed by POLICY_DEEP_2 or POLICY_DEEP_3. | 54 # DUMP_DEEP_[3-4] should be processed by POLICY_DEEP_2 or POLICY_DEEP_3. |
(...skipping 159 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
213 files by 'prepare()' with tools/find_runtime_symbols/prepare_symbol_info.py. | 214 files by 'prepare()' with tools/find_runtime_symbols/prepare_symbol_info.py. |
214 | 215 |
215 Binaries are not mandatory to profile. The prepared data sources work in | 216 Binaries are not mandatory to profile. The prepared data sources work in |
216 place of the binary even if the binary has been overwritten with another | 217 place of the binary even if the binary has been overwritten with another |
217 binary. | 218 binary. |
218 | 219 |
219 Note that loading the symbol data sources takes a long time. They are often | 220 Note that loading the symbol data sources takes a long time. They are often |
220 very big. So, the 'dmprof' profiler is designed to use 'SymbolMappingCache' | 221 very big. So, the 'dmprof' profiler is designed to use 'SymbolMappingCache' |
221 which caches actually used symbols. | 222 which caches actually used symbols. |
222 """ | 223 """ |
223 def __init__(self, prefix, fake_directories=None): | 224 def __init__(self, prefix, alternative_dirs=None): |
224 self._prefix = prefix | 225 self._prefix = prefix |
225 self._prepared_symbol_data_sources_path = None | 226 self._prepared_symbol_data_sources_path = None |
226 self._loaded_symbol_data_sources = None | 227 self._loaded_symbol_data_sources = None |
227 self._fake_directories = fake_directories or {} | 228 self._alternative_dirs = alternative_dirs or {} |
228 | 229 |
229 def prepare(self): | 230 def prepare(self): |
230 """Prepares symbol data sources by extracting mapping from a binary. | 231 """Prepares symbol data sources by extracting mapping from a binary. |
231 | 232 |
232 The prepared symbol data sources are stored in a directory. The directory | 233 The prepared symbol data sources are stored in a directory. The directory |
233 name is stored in |self._prepared_symbol_data_sources_path|. | 234 name is stored in |self._prepared_symbol_data_sources_path|. |
234 | 235 |
235 Returns: | 236 Returns: |
236 True if succeeded. | 237 True if succeeded. |
237 """ | 238 """ |
238 LOGGER.info('Preparing symbol mapping...') | 239 LOGGER.info('Preparing symbol mapping...') |
239 self._prepared_symbol_data_sources_path, used_tempdir = ( | 240 self._prepared_symbol_data_sources_path, used_tempdir = ( |
240 prepare_symbol_info.prepare_symbol_info( | 241 prepare_symbol_info.prepare_symbol_info( |
241 self._prefix + '.maps', | 242 self._prefix + '.maps', |
242 output_dir_path=self._prefix + '.symmap', | 243 output_dir_path=self._prefix + '.symmap', |
243 fake_directories=self._fake_directories, | 244 alternative_dirs=self._alternative_dirs, |
244 use_tempdir=True, | 245 use_tempdir=True, |
245 use_source_file_name=True)) | 246 use_source_file_name=True)) |
246 if self._prepared_symbol_data_sources_path: | 247 if self._prepared_symbol_data_sources_path: |
247 LOGGER.info(' Prepared symbol mapping.') | 248 LOGGER.info(' Prepared symbol mapping.') |
248 if used_tempdir: | 249 if used_tempdir: |
249 LOGGER.warn(' Using a temporary directory for symbol mapping.') | 250 LOGGER.warn(' Using a temporary directory for symbol mapping.') |
250 LOGGER.warn(' Delete it by yourself.') | 251 LOGGER.warn(' Delete it by yourself.') |
251 LOGGER.warn(' Or, move the directory by yourself to use it later.') | 252 LOGGER.warn(' Or, move the directory by yourself to use it later.') |
252 return True | 253 return True |
253 else: | 254 else: |
(...skipping 789 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1043 | 1044 |
1044 def __getitem__(self, index): | 1045 def __getitem__(self, index): |
1045 return self._dump_list[index] | 1046 return self._dump_list[index] |
1046 | 1047 |
1047 | 1048 |
1048 class Command(object): | 1049 class Command(object): |
1049 """Subclasses are a subcommand for this executable. | 1050 """Subclasses are a subcommand for this executable. |
1050 | 1051 |
1051 See COMMANDS in main(). | 1052 See COMMANDS in main(). |
1052 """ | 1053 """ |
1054 _DEVICE_LIB_PATTERN = re.compile(r'(/data/app-lib/.*-[0-9])/.*') | |
1055 | |
1053 def __init__(self, usage): | 1056 def __init__(self, usage): |
1054 self._parser = optparse.OptionParser(usage) | 1057 self._parser = optparse.OptionParser(usage) |
1055 | 1058 |
1056 @staticmethod | 1059 @staticmethod |
1057 def load_basic_files( | 1060 def load_basic_files( |
1058 dump_path, multiple, no_dump=False, fake_directories=None): | 1061 dump_path, multiple, no_dump=False, alternative_dirs=None): |
1059 prefix = Command._find_prefix(dump_path) | 1062 prefix = Command._find_prefix(dump_path) |
1060 symbol_data_sources = SymbolDataSources(prefix, fake_directories or {}) | 1063 # Translate the symbol information from the Android device to the |
peria
2013/05/15 17:03:21
I'm not sure, but does this method work only for A
Dai Mikurube (NOT FULLTIME)
2013/05/15 22:44:38
This method is called even for Linux (not Android)
| |
1064 # corresponding files in the host. The location of the files in the host | |
1065 # is estimated from the binary path on the device. Use --alternative-dirs | |
1066 # to specify the location manually. | |
1067 if not alternative_dirs: | |
1068 alternative_dirs = Command._estimate_alternative_dirs(prefix) | |
1069 if alternative_dirs: | |
1070 for device, host in alternative_dirs.iteritems(): | |
1071 LOGGER.info('Assuming %s on device as %s on host' % (device, host)) | |
1072 symbol_data_sources = SymbolDataSources(prefix, alternative_dirs) | |
1061 symbol_data_sources.prepare() | 1073 symbol_data_sources.prepare() |
1062 bucket_set = BucketSet() | 1074 bucket_set = BucketSet() |
1063 bucket_set.load(prefix) | 1075 bucket_set.load(prefix) |
1064 if not no_dump: | 1076 if not no_dump: |
1065 if multiple: | 1077 if multiple: |
1066 dump_list = DumpList.load(Command._find_all_dumps(dump_path)) | 1078 dump_list = DumpList.load(Command._find_all_dumps(dump_path)) |
1067 else: | 1079 else: |
1068 dump = Dump.load(dump_path) | 1080 dump = Dump.load(dump_path) |
1069 symbol_mapping_cache = SymbolMappingCache() | 1081 symbol_mapping_cache = SymbolMappingCache() |
1070 with open(prefix + '.cache.function', 'a+') as cache_f: | 1082 with open(prefix + '.cache.function', 'a+') as cache_f: |
(...skipping 14 matching lines...) Expand all Loading... | |
1085 elif multiple: | 1097 elif multiple: |
1086 return (bucket_set, dump_list) | 1098 return (bucket_set, dump_list) |
1087 else: | 1099 else: |
1088 return (bucket_set, dump) | 1100 return (bucket_set, dump) |
1089 | 1101 |
1090 @staticmethod | 1102 @staticmethod |
1091 def _find_prefix(path): | 1103 def _find_prefix(path): |
1092 return re.sub('\.[0-9][0-9][0-9][0-9]\.heap', '', path) | 1104 return re.sub('\.[0-9][0-9][0-9][0-9]\.heap', '', path) |
1093 | 1105 |
1094 @staticmethod | 1106 @staticmethod |
1107 def _estimate_alternative_dirs(prefix): | |
1108 """Estimates a corresponding host directory for a device-local directory. | |
1109 | |
1110 For Android, dmprof.py should find symbol information from the binary in | |
1111 the host, not in the Android device, because dmprof.py doesn't run on | |
1112 Android devices. This method estimates the lib directory in the host | |
1113 corresponding to the directory in the Android device. | |
1114 | |
1115 Returns: | |
1116 A dict that maps a path in the Android device to a path in host. | |
1117 If a file in "/data/app-lib" found in /proc/maps, it assumes the | |
1118 process was running on Android. An empty dict unless Android. | |
1119 """ | |
1120 device_lib_path_candidates = set() | |
1121 | |
1122 with open(prefix + '.maps') as maps_f: | |
1123 maps = proc_maps.ProcMaps.load(maps_f) | |
1124 for entry in maps: | |
1125 matched = Command._DEVICE_LIB_PATTERN.match(entry.as_dict()['name']) | |
1126 if matched: | |
1127 device_lib_path_candidates.add(matched.group(1)) | |
1128 | |
1129 if len(device_lib_path_candidates) == 1: | |
1130 return {device_lib_path_candidates.pop(): os.path.join( | |
1131 CHROME_SRC_PATH, 'out', 'Debug', 'lib')} | |
1132 else: | |
1133 return {} | |
1134 | |
1135 @staticmethod | |
1095 def _find_all_dumps(dump_path): | 1136 def _find_all_dumps(dump_path): |
1096 prefix = Command._find_prefix(dump_path) | 1137 prefix = Command._find_prefix(dump_path) |
1097 dump_path_list = [dump_path] | 1138 dump_path_list = [dump_path] |
1098 | 1139 |
1099 n = int(dump_path[len(dump_path) - 9 : len(dump_path) - 5]) | 1140 n = int(dump_path[len(dump_path) - 9 : len(dump_path) - 5]) |
1100 n += 1 | 1141 n += 1 |
1101 while True: | 1142 while True: |
1102 p = '%s.%04d.heap' % (prefix, n) | 1143 p = '%s.%04d.heap' % (prefix, n) |
1103 if os.path.exists(p): | 1144 if os.path.exists(p): |
1104 dump_path_list.append(p) | 1145 dump_path_list.append(p) |
(...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1197 out.write(frame + ' ') | 1238 out.write(frame + ' ') |
1198 out.write('\n') | 1239 out.write('\n') |
1199 | 1240 |
1200 | 1241 |
1201 class PolicyCommands(Command): | 1242 class PolicyCommands(Command): |
1202 def __init__(self, command): | 1243 def __init__(self, command): |
1203 super(PolicyCommands, self).__init__( | 1244 super(PolicyCommands, self).__init__( |
1204 'Usage: %%prog %s [-p POLICY] <first-dump>' % command) | 1245 'Usage: %%prog %s [-p POLICY] <first-dump>' % command) |
1205 self._parser.add_option('-p', '--policy', type='string', dest='policy', | 1246 self._parser.add_option('-p', '--policy', type='string', dest='policy', |
1206 help='profile with POLICY', metavar='POLICY') | 1247 help='profile with POLICY', metavar='POLICY') |
1207 self._parser.add_option('--fake-directories', dest='fake_directories', | 1248 self._parser.add_option('--alternative-dirs', dest='alternative_dirs', |
1208 metavar='/path/on/target@/path/on/host[:...]', | 1249 metavar='/path/on/target@/path/on/host[:...]', |
1209 help='Read files in /path/on/host/ instead of ' | 1250 help='Read files in /path/on/host/ instead of ' |
1210 'files in /path/on/target/.') | 1251 'files in /path/on/target/.') |
1211 | 1252 |
1212 def _set_up(self, sys_argv): | 1253 def _set_up(self, sys_argv): |
1213 options, args = self._parse_args(sys_argv, 1) | 1254 options, args = self._parse_args(sys_argv, 1) |
1214 dump_path = args[1] | 1255 dump_path = args[1] |
1215 fake_directories_dict = {} | 1256 alternative_dirs_dict = {} |
1216 if options.fake_directories: | 1257 if options.alternative_dirs: |
1217 for fake_directory_pair in options.fake_directories.split(':'): | 1258 for alternative_dir_pair in options.alternative_dirs.split(':'): |
1218 target_path, host_path = fake_directory_pair.split('@', 1) | 1259 target_path, host_path = alternative_dir_pair.split('@', 1) |
1219 fake_directories_dict[target_path] = host_path | 1260 alternative_dirs_dict[target_path] = host_path |
1220 (bucket_set, dumps) = Command.load_basic_files( | 1261 (bucket_set, dumps) = Command.load_basic_files( |
1221 dump_path, True, fake_directories=fake_directories_dict) | 1262 dump_path, True, alternative_dirs=alternative_dirs_dict) |
1222 | 1263 |
1223 policy_set = PolicySet.load(Command._parse_policy_list(options.policy)) | 1264 policy_set = PolicySet.load(Command._parse_policy_list(options.policy)) |
1224 return policy_set, dumps, bucket_set | 1265 return policy_set, dumps, bucket_set |
1225 | 1266 |
1226 @staticmethod | 1267 @staticmethod |
1227 def _apply_policy(dump, policy, bucket_set, first_dump_time): | 1268 def _apply_policy(dump, policy, bucket_set, first_dump_time): |
1228 """Aggregates the total memory size of each component. | 1269 """Aggregates the total memory size of each component. |
1229 | 1270 |
1230 Iterate through all stacktraces and attribute them to one of the components | 1271 Iterate through all stacktraces and attribute them to one of the components |
1231 based on the policy. It is important to apply policy in right order. | 1272 based on the policy. It is important to apply policy in right order. |
(...skipping 538 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1770 errorcode = COMMANDS[action]().do(sys.argv) | 1811 errorcode = COMMANDS[action]().do(sys.argv) |
1771 except ParsingException, e: | 1812 except ParsingException, e: |
1772 errorcode = 1 | 1813 errorcode = 1 |
1773 sys.stderr.write('Exit by parsing error: %s\n' % e) | 1814 sys.stderr.write('Exit by parsing error: %s\n' % e) |
1774 | 1815 |
1775 return errorcode | 1816 return errorcode |
1776 | 1817 |
1777 | 1818 |
1778 if __name__ == '__main__': | 1819 if __name__ == '__main__': |
1779 sys.exit(main()) | 1820 sys.exit(main()) |
OLD | NEW |