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 json 6import logging 7import sys 8 9from lib.bucket import BUCKET_ID, COMMITTED, ALLOC_COUNT, FREE_COUNT 10from lib.ordered_dict import OrderedDict 11from lib.subcommand import SubCommand 12from lib.sorter import MallocUnit, MMapUnit, SorterSet, UnhookedUnit, UnitSet 13 14 15LOGGER = logging.getLogger('dmprof') 16 17 18class CatCommand(SubCommand): 19 def __init__(self): 20 super(CatCommand, self).__init__('Usage: %prog cat <first-dump>') 21 self._parser.add_option('--alternative-dirs', dest='alternative_dirs', 22 metavar='/path/on/target@/path/on/host[:...]', 23 help='Read files in /path/on/host/ instead of ' 24 'files in /path/on/target/.') 25 self._parser.add_option('--indent', dest='indent', action='store_true', 26 help='Indent the output.') 27 28 def do(self, sys_argv): 29 options, args = self._parse_args(sys_argv, 1) 30 dump_path = args[1] 31 # TODO(dmikurube): Support shared memory. 32 alternative_dirs_dict = {} 33 if options.alternative_dirs: 34 for alternative_dir_pair in options.alternative_dirs.split(':'): 35 target_path, host_path = alternative_dir_pair.split('@', 1) 36 alternative_dirs_dict[target_path] = host_path 37 (bucket_set, dumps) = SubCommand.load_basic_files( 38 dump_path, True, alternative_dirs=alternative_dirs_dict) 39 40 # Load all sorters. 41 sorters = SorterSet() 42 43 json_root = OrderedDict() 44 json_root['version'] = 1 45 json_root['run_id'] = None 46 for dump in dumps: 47 if json_root['run_id'] and json_root['run_id'] != dump.run_id: 48 LOGGER.error('Inconsistent heap profile dumps.') 49 json_root['run_id'] = '' 50 break 51 json_root['run_id'] = dump.run_id 52 json_root['roots'] = [] 53 for sorter in sorters: 54 if sorter.root: 55 json_root['roots'].append([sorter.world, sorter.name]) 56 json_root['default_template'] = 'l2' 57 json_root['templates'] = sorters.templates.as_dict() 58 59 orders = OrderedDict() 60 orders['worlds'] = OrderedDict() 61 for world in ['vm', 'malloc']: 62 orders['worlds'][world] = OrderedDict() 63 orders['worlds'][world]['breakdown'] = OrderedDict() 64 for sorter in sorters.iter_world(world): 65 order = [] 66 for rule in sorter.iter_rule(): 67 if rule.name not in order: 68 order.append(rule.name) 69 orders['worlds'][world]['breakdown'][sorter.name] = order 70 json_root['orders'] = orders 71 72 json_root['snapshots'] = [] 73 74 for dump in dumps: 75 LOGGER.info('Sorting a dump %s...' % dump.path) 76 json_root['snapshots'].append( 77 self._fill_snapshot(dump, bucket_set, sorters)) 78 79 if options.indent: 80 json.dump(json_root, sys.stdout, indent=2) 81 else: 82 json.dump(json_root, sys.stdout) 83 print '' 84 85 @staticmethod 86 def _fill_snapshot(dump, bucket_set, sorters): 87 root = OrderedDict() 88 root['time'] = dump.time 89 root['worlds'] = OrderedDict() 90 root['worlds']['vm'] = CatCommand._fill_world( 91 dump, bucket_set, sorters, 'vm') 92 root['worlds']['malloc'] = CatCommand._fill_world( 93 dump, bucket_set, sorters, 'malloc') 94 return root 95 96 @staticmethod 97 def _fill_world(dump, bucket_set, sorters, world): 98 root = OrderedDict() 99 100 root['name'] = world 101 if world == 'vm': 102 root['unit_fields'] = ['size', 'reserved'] 103 elif world == 'malloc': 104 root['unit_fields'] = ['size', 'alloc_count', 'free_count'] 105 106 # Make { vm | malloc } units with their sizes. 107 root['units'] = OrderedDict() 108 unit_set = UnitSet(world) 109 if world == 'vm': 110 for unit in CatCommand._iterate_vm_unit(dump, None, bucket_set): 111 unit_set.append(unit) 112 for unit in unit_set: 113 root['units'][unit.unit_id] = [unit.committed, unit.reserved] 114 elif world == 'malloc': 115 for unit in CatCommand._iterate_malloc_unit(dump, bucket_set): 116 unit_set.append(unit) 117 for unit in unit_set: 118 root['units'][unit.unit_id] = [ 119 unit.size, unit.alloc_count, unit.free_count] 120 121 # Iterate for { vm | malloc } sorters. 122 root['breakdown'] = OrderedDict() 123 for sorter in sorters.iter_world(world): 124 LOGGER.info(' Sorting with %s:%s.' % (sorter.world, sorter.name)) 125 breakdown = OrderedDict() 126 for rule in sorter.iter_rule(): 127 category = OrderedDict() 128 category['name'] = rule.name 129 subs = [] 130 for sub_world, sub_breakdown in rule.iter_subs(): 131 subs.append([sub_world, sub_breakdown]) 132 if subs: 133 category['subs'] = subs 134 if rule.hidden: 135 category['hidden'] = True 136 category['units'] = [] 137 breakdown[rule.name] = category 138 for unit in unit_set: 139 found = sorter.find(unit) 140 if found: 141 # Note that a bucket which doesn't match any rule is just dropped. 142 breakdown[found.name]['units'].append(unit.unit_id) 143 root['breakdown'][sorter.name] = breakdown 144 145 return root 146 147 @staticmethod 148 def _iterate_vm_unit(dump, pfn_dict, bucket_set): 149 unit_id = 0 150 for _, region in dump.iter_map: 151 unit_id += 1 152 if region[0] == 'unhooked': 153 if pfn_dict and dump.pageframe_length: 154 for pageframe in region[1]['pageframe']: 155 yield UnhookedUnit(unit_id, pageframe.size, pageframe.size, 156 region, pageframe, pfn_dict) 157 else: 158 yield UnhookedUnit(unit_id, 159 int(region[1]['committed']), 160 int(region[1]['reserved']), 161 region) 162 elif region[0] == 'hooked': 163 if pfn_dict and dump.pageframe_length: 164 for pageframe in region[1]['pageframe']: 165 yield MMapUnit(unit_id, 166 pageframe.size, 167 pageframe.size, 168 region, bucket_set, pageframe, pfn_dict) 169 else: 170 yield MMapUnit(unit_id, 171 int(region[1]['committed']), 172 int(region[1]['reserved']), 173 region, 174 bucket_set) 175 else: 176 LOGGER.error('Unrecognized mapping status: %s' % region[0]) 177 178 @staticmethod 179 def _iterate_malloc_unit(dump, bucket_set): 180 for line in dump.iter_stacktrace: 181 words = line.split() 182 bucket = bucket_set.get(int(words[BUCKET_ID])) 183 if bucket and bucket.allocator_type == 'malloc': 184 yield MallocUnit(int(words[BUCKET_ID]), 185 int(words[COMMITTED]), 186 int(words[ALLOC_COUNT]), 187 int(words[FREE_COUNT]), 188 bucket) 189 elif not bucket: 190 # 'Not-found' buckets are all assumed as malloc buckets. 191 yield MallocUnit(int(words[BUCKET_ID]), 192 int(words[COMMITTED]), 193 int(words[ALLOC_COUNT]), 194 int(words[FREE_COUNT]), 195 None) 196