Home | History | Annotate | Download | only in lib
      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 
      5 import cStringIO
      6 import json
      7 import logging
      8 import os
      9 import re
     10 
     11 
     12 LOGGER = logging.getLogger('dmprof')
     13 
     14 BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
     15 
     16 DEFAULT_SORTERS = [
     17     os.path.join(BASE_PATH, 'sorter.malloc-component.json'),
     18     os.path.join(BASE_PATH, 'sorter.malloc-type.json'),
     19     os.path.join(BASE_PATH, 'sorter.vm-map.json'),
     20     os.path.join(BASE_PATH, 'sorter.vm-sharing.json'),
     21     ]
     22 
     23 DEFAULT_TEMPLATES = os.path.join(BASE_PATH, 'templates.json')
     24 
     25 
     26 class Unit(object):
     27   """Represents a minimum unit of memory usage categorization.
     28 
     29   It is supposed to be inherited for some different spaces like the entire
     30   virtual memory and malloc arena. Such different spaces are called "worlds"
     31   in dmprof. (For example, the "vm" world and the "malloc" world.)
     32   """
     33   def __init__(self, unit_id, size):
     34     self._unit_id = unit_id
     35     self._size = size
     36 
     37   @property
     38   def unit_id(self):
     39     return self._unit_id
     40 
     41   @property
     42   def size(self):
     43     return self._size
     44 
     45 
     46 class VMUnit(Unit):
     47   """Represents a Unit for a memory region on virtual memory."""
     48   def __init__(self, unit_id, committed, reserved, mmap, region,
     49                pageframe=None, group_pfn_counts=None):
     50     super(VMUnit, self).__init__(unit_id, committed)
     51     self._reserved = reserved
     52     self._mmap = mmap
     53     self._region = region
     54     self._pageframe = pageframe
     55     self._group_pfn_counts = group_pfn_counts
     56 
     57   @property
     58   def committed(self):
     59     return self._size
     60 
     61   @property
     62   def reserved(self):
     63     return self._reserved
     64 
     65   @property
     66   def mmap(self):
     67     return self._mmap
     68 
     69   @property
     70   def region(self):
     71     return self._region
     72 
     73   @property
     74   def pageframe(self):
     75     return self._pageframe
     76 
     77   @property
     78   def group_pfn_counts(self):
     79     return self._group_pfn_counts
     80 
     81 
     82 class MMapUnit(VMUnit):
     83   """Represents a Unit for a mmap'ed region."""
     84   def __init__(self, unit_id, committed, reserved, region, bucket_set,
     85                pageframe=None, group_pfn_counts=None):
     86     super(MMapUnit, self).__init__(unit_id, committed, reserved, True,
     87                                    region, pageframe, group_pfn_counts)
     88     self._bucket_set = bucket_set
     89 
     90   def __repr__(self):
     91     return str(self.region)
     92 
     93   @property
     94   def bucket_set(self):
     95     return self._bucket_set
     96 
     97 
     98 class UnhookedUnit(VMUnit):
     99   """Represents a Unit for a non-mmap'ed memory region on virtual memory."""
    100   def __init__(self, unit_id, committed, reserved, region,
    101                pageframe=None, group_pfn_counts=None):
    102     super(UnhookedUnit, self).__init__(unit_id, committed, reserved, False,
    103                                        region, pageframe, group_pfn_counts)
    104 
    105   def __repr__(self):
    106     return str(self.region)
    107 
    108 
    109 class MallocUnit(Unit):
    110   """Represents a Unit for a malloc'ed memory block."""
    111   def __init__(self, unit_id, size, alloc_count, free_count, bucket):
    112     super(MallocUnit, self).__init__(unit_id, size)
    113     self._bucket = bucket
    114     self._alloc_count = alloc_count
    115     self._free_count = free_count
    116 
    117   def __repr__(self):
    118     return str(self.bucket)
    119 
    120   @property
    121   def bucket(self):
    122     return self._bucket
    123 
    124   @property
    125   def alloc_count(self):
    126     return self._alloc_count
    127 
    128   @property
    129   def free_count(self):
    130     return self._free_count
    131 
    132 
    133 class UnitSet(object):
    134   """Represents an iterable set of Units."""
    135   def __init__(self, world):
    136     self._units = {}
    137     self._world = world
    138 
    139   def __repr__(self):
    140     return str(self._units)
    141 
    142   def __iter__(self):
    143     for unit_id in sorted(self._units):
    144       yield self._units[unit_id]
    145 
    146   def append(self, unit, overwrite=False):
    147     if not overwrite and unit.unit_id in self._units:
    148       LOGGER.error('The unit id=%s already exists.' % str(unit.unit_id))
    149     self._units[unit.unit_id] = unit
    150 
    151 
    152 class AbstractRule(object):
    153   """An abstract class for rules to be matched with units."""
    154   def __init__(self, dct):
    155     self._name = dct['name']
    156     self._hidden = dct.get('hidden', False)
    157     self._subs = dct.get('subs', [])
    158 
    159   def match(self, unit):
    160     raise NotImplementedError()
    161 
    162   @property
    163   def name(self):
    164     return self._name
    165 
    166   @property
    167   def hidden(self):
    168     return self._hidden
    169 
    170   def iter_subs(self):
    171     for sub in self._subs:
    172       yield sub
    173 
    174 
    175 class VMRule(AbstractRule):
    176   """Represents a Rule to match with virtual memory regions."""
    177   def __init__(self, dct):
    178     super(VMRule, self).__init__(dct)
    179     self._backtrace_function = dct.get('backtrace_function', None)
    180     if self._backtrace_function:
    181       self._backtrace_function = re.compile(self._backtrace_function)
    182     self._backtrace_sourcefile = dct.get('backtrace_sourcefile', None)
    183     if self._backtrace_sourcefile:
    184       self._backtrace_sourcefile = re.compile(self._backtrace_sourcefile)
    185     self._mmap = dct.get('mmap', None)
    186     self._sharedwith = dct.get('sharedwith', [])
    187     self._mapped_pathname = dct.get('mapped_pathname', None)
    188     if self._mapped_pathname:
    189       self._mapped_pathname = re.compile(self._mapped_pathname)
    190     self._mapped_permission = dct.get('mapped_permission', None)
    191     if self._mapped_permission:
    192       self._mapped_permission = re.compile(self._mapped_permission)
    193 
    194   def __repr__(self):
    195     result = cStringIO.StringIO()
    196     result.write('{"%s"=>' % self._name)
    197     attributes = []
    198     attributes.append('mmap: %s' % self._mmap)
    199     if self._backtrace_function:
    200       attributes.append('backtrace_function: "%s"' %
    201                         self._backtrace_function.pattern)
    202     if self._sharedwith:
    203       attributes.append('sharedwith: "%s"' % self._sharedwith)
    204     if self._mapped_pathname:
    205       attributes.append('mapped_pathname: "%s"' % self._mapped_pathname.pattern)
    206     if self._mapped_permission:
    207       attributes.append('mapped_permission: "%s"' %
    208                         self._mapped_permission.pattern)
    209     result.write('%s}' % ', '.join(attributes))
    210     return result.getvalue()
    211 
    212   def match(self, unit):
    213     if unit.mmap:
    214       assert unit.region[0] == 'hooked'
    215       bucket = unit.bucket_set.get(unit.region[1]['bucket_id'])
    216       assert bucket
    217       assert bucket.allocator_type == 'mmap'
    218 
    219       stackfunction = bucket.symbolized_joined_stackfunction
    220       stacksourcefile = bucket.symbolized_joined_stacksourcefile
    221 
    222       # TODO(dmikurube): Support shared memory.
    223       sharedwith = None
    224 
    225       if self._mmap == False: # (self._mmap == None) should go through.
    226         return False
    227       if (self._backtrace_function and
    228           not self._backtrace_function.match(stackfunction)):
    229         return False
    230       if (self._backtrace_sourcefile and
    231           not self._backtrace_sourcefile.match(stacksourcefile)):
    232         return False
    233       if (self._mapped_pathname and
    234           not self._mapped_pathname.match(unit.region[1]['vma']['name'])):
    235         return False
    236       if (self._mapped_permission and
    237           not self._mapped_permission.match(
    238               unit.region[1]['vma']['readable'] +
    239               unit.region[1]['vma']['writable'] +
    240               unit.region[1]['vma']['executable'] +
    241               unit.region[1]['vma']['private'])):
    242         return False
    243       if (self._sharedwith and
    244           unit.pageframe and sharedwith not in self._sharedwith):
    245         return False
    246 
    247       return True
    248 
    249     else:
    250       assert unit.region[0] == 'unhooked'
    251 
    252       # TODO(dmikurube): Support shared memory.
    253       sharedwith = None
    254 
    255       if self._mmap == True: # (self._mmap == None) should go through.
    256         return False
    257       if (self._mapped_pathname and
    258           not self._mapped_pathname.match(unit.region[1]['vma']['name'])):
    259         return False
    260       if (self._mapped_permission and
    261           not self._mapped_permission.match(
    262               unit.region[1]['vma']['readable'] +
    263               unit.region[1]['vma']['writable'] +
    264               unit.region[1]['vma']['executable'] +
    265               unit.region[1]['vma']['private'])):
    266         return False
    267       if (self._sharedwith and
    268           unit.pageframe and sharedwith not in self._sharedwith):
    269         return False
    270 
    271       return True
    272 
    273 
    274 class MallocRule(AbstractRule):
    275   """Represents a Rule to match with malloc'ed blocks."""
    276   def __init__(self, dct):
    277     super(MallocRule, self).__init__(dct)
    278     self._backtrace_function = dct.get('backtrace_function', None)
    279     if self._backtrace_function:
    280       self._backtrace_function = re.compile(self._backtrace_function)
    281     self._backtrace_sourcefile = dct.get('backtrace_sourcefile', None)
    282     if self._backtrace_sourcefile:
    283       self._backtrace_sourcefile = re.compile(self._backtrace_sourcefile)
    284     self._typeinfo = dct.get('typeinfo', None)
    285     if self._typeinfo:
    286       self._typeinfo = re.compile(self._typeinfo)
    287 
    288   def __repr__(self):
    289     result = cStringIO.StringIO()
    290     result.write('{"%s"=>' % self._name)
    291     attributes = []
    292     if self._backtrace_function:
    293       attributes.append('backtrace_function: "%s"' % self._backtrace_function)
    294     if self._typeinfo:
    295       attributes.append('typeinfo: "%s"' % self._typeinfo)
    296     result.write('%s}' % ', '.join(attributes))
    297     return result.getvalue()
    298 
    299   def match(self, unit):
    300     assert unit.bucket.allocator_type == 'malloc'
    301 
    302     stackfunction = unit.bucket.symbolized_joined_stackfunction
    303     stacksourcefile = unit.bucket.symbolized_joined_stacksourcefile
    304     typeinfo = unit.bucket.symbolized_typeinfo
    305     if typeinfo.startswith('0x'):
    306       typeinfo = unit.bucket.typeinfo_name
    307 
    308     return ((not self._backtrace_function or
    309              self._backtrace_function.match(stackfunction)) and
    310             (not self._backtrace_sourcefile or
    311              self._backtrace_sourcefile.match(stacksourcefile)) and
    312             (not self._typeinfo or self._typeinfo.match(typeinfo)))
    313 
    314 
    315 class NoBucketMallocRule(MallocRule):
    316   """Represents a Rule that small ignorable units match with."""
    317   def __init__(self):
    318     super(NoBucketMallocRule, self).__init__({'name': 'tc-no-bucket'})
    319     self._no_bucket = True
    320 
    321   @property
    322   def no_bucket(self):
    323     return self._no_bucket
    324 
    325 
    326 class AbstractSorter(object):
    327   """An abstract class for classifying Units with a set of Rules."""
    328   def __init__(self, dct):
    329     self._type = 'sorter'
    330     self._version = dct['version']
    331     self._world = dct['world']
    332     self._name = dct['name']
    333     self._root = dct.get('root', False)
    334     self._order = dct['order']
    335 
    336     self._rules = []
    337     for rule in dct['rules']:
    338       if dct['world'] == 'vm':
    339         self._rules.append(VMRule(rule))
    340       elif dct['world'] == 'malloc':
    341         self._rules.append(MallocRule(rule))
    342       else:
    343         LOGGER.error('Unknown sorter world type')
    344 
    345   def __repr__(self):
    346     result = cStringIO.StringIO()
    347     result.write('world=%s' % self._world)
    348     result.write('order=%s' % self._order)
    349     result.write('rules:')
    350     for rule in self._rules:
    351       result.write('  %s' % rule)
    352     return result.getvalue()
    353 
    354   @staticmethod
    355   def load(filename):
    356     with open(filename) as sorter_f:
    357       sorter_dict = json.load(sorter_f)
    358     if sorter_dict['world'] == 'vm':
    359       return VMSorter(sorter_dict)
    360     elif sorter_dict['world'] == 'malloc':
    361       return MallocSorter(sorter_dict)
    362     else:
    363       LOGGER.error('Unknown sorter world type')
    364       return None
    365 
    366   @property
    367   def world(self):
    368     return self._world
    369 
    370   @property
    371   def name(self):
    372     return self._name
    373 
    374   @property
    375   def root(self):
    376     return self._root
    377 
    378   def find(self, unit):
    379     raise NotImplementedError()
    380 
    381   def find_rule(self, name):
    382     """Finds a rule whose name is |name|. """
    383     for rule in self._rules:
    384       if rule.name == name:
    385         return rule
    386     return None
    387 
    388 
    389 class VMSorter(AbstractSorter):
    390   """Represents a Sorter for memory regions on virtual memory."""
    391   def __init__(self, dct):
    392     assert dct['world'] == 'vm'
    393     super(VMSorter, self).__init__(dct)
    394 
    395   def find(self, unit):
    396     for rule in self._rules:
    397       if rule.match(unit):
    398         return rule
    399     assert False
    400 
    401 
    402 class MallocSorter(AbstractSorter):
    403   """Represents a Sorter for malloc'ed blocks."""
    404   def __init__(self, dct):
    405     assert dct['world'] == 'malloc'
    406     super(MallocSorter, self).__init__(dct)
    407     self._no_bucket_rule = NoBucketMallocRule()
    408 
    409   def find(self, unit):
    410     if not unit.bucket:
    411       return self._no_bucket_rule
    412     assert unit.bucket.allocator_type == 'malloc'
    413 
    414     if unit.bucket.component_cache:
    415       return unit.bucket.component_cache
    416 
    417     for rule in self._rules:
    418       if rule.match(unit):
    419         unit.bucket.component_cache = rule
    420         return rule
    421     assert False
    422 
    423 
    424 class SorterTemplates(object):
    425   """Represents a template for sorters."""
    426   def __init__(self, dct):
    427     self._dict = dct
    428 
    429   def as_dict(self):
    430     return self._dict
    431 
    432   @staticmethod
    433   def load(filename):
    434     with open(filename) as templates_f:
    435       templates_dict = json.load(templates_f)
    436     return SorterTemplates(templates_dict)
    437 
    438 
    439 class SorterSet(object):
    440   """Represents an iterable set of Sorters."""
    441   def __init__(self, additional=None, default=None):
    442     if not additional:
    443       additional = []
    444     if not default:
    445       default = DEFAULT_SORTERS
    446     self._sorters = {}
    447     for filename in default + additional:
    448       sorter = AbstractSorter.load(filename)
    449       if sorter.world not in self._sorters:
    450         self._sorters[sorter.world] = []
    451       self._sorters[sorter.world].append(sorter)
    452     self._templates = SorterTemplates.load(DEFAULT_TEMPLATES)
    453 
    454   def __repr__(self):
    455     result = cStringIO.StringIO()
    456     result.write(self._sorters)
    457     return result.getvalue()
    458 
    459   def __iter__(self):
    460     for sorters in self._sorters.itervalues():
    461       for sorter in sorters:
    462         yield sorter
    463 
    464   def iter_world(self, world):
    465     for sorter in self._sorters.get(world, []):
    466       yield sorter
    467 
    468   @property
    469   def templates(self):
    470     return self._templates
    471