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    json_root['roots'] = []
47    for sorter in sorters:
48      if sorter.root:
49        json_root['roots'].append([sorter.world, sorter.name])
50    json_root['default_template'] = 'l2'
51    json_root['templates'] = sorters.templates.as_dict()
52
53    orders = OrderedDict()
54    orders['worlds'] = OrderedDict()
55    for world in ['vm', 'malloc']:
56      orders['worlds'][world] = OrderedDict()
57      orders['worlds'][world]['breakdown'] = OrderedDict()
58      for sorter in sorters.iter_world(world):
59        order = []
60        for rule in sorter.iter_rule():
61          if rule.name not in order:
62            order.append(rule.name)
63        orders['worlds'][world]['breakdown'][sorter.name] = order
64    json_root['orders'] = orders
65
66    json_root['snapshots'] = []
67
68    for dump in dumps:
69      if json_root['run_id'] and json_root['run_id'] != dump.run_id:
70        LOGGER.error('Inconsistent heap profile dumps.')
71        json_root['run_id'] = ''
72      else:
73        json_root['run_id'] = dump.run_id
74
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