| Index: tools/deep_memory_profiler/lib/sorter.py
|
| diff --git a/tools/deep_memory_profiler/lib/sorter.py b/tools/deep_memory_profiler/lib/sorter.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..db50c70ad71499ffa610439d50da4f9a6c6a97fc
|
| --- /dev/null
|
| +++ b/tools/deep_memory_profiler/lib/sorter.py
|
| @@ -0,0 +1,443 @@
|
| +# Copyright 2013 The Chromium Authors. All rights reserved.
|
| +# Use of this source code is governed by a BSD-style license that can be
|
| +# found in the LICENSE file.
|
| +
|
| +import cStringIO
|
| +import json
|
| +import logging
|
| +import os
|
| +import re
|
| +
|
| +
|
| +LOGGER = logging.getLogger('dmprof')
|
| +
|
| +BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| +
|
| +DEFAULT_SORTERS = [
|
| + os.path.join(BASE_PATH, 'sorter.malloc-component.json'),
|
| + os.path.join(BASE_PATH, 'sorter.malloc-type.json'),
|
| + os.path.join(BASE_PATH, 'sorter.vm-map.json'),
|
| + os.path.join(BASE_PATH, 'sorter.vm-sharing.json'),
|
| + ]
|
| +
|
| +
|
| +class Unit(object):
|
| + """Represents a minimum unit of memory usage categorization.
|
| +
|
| + It is supposed to be inherited for some different spaces like the entire
|
| + virtual memory and malloc arena. Such different spaces are called "worlds"
|
| + in dmprof. (For example, the "vm" world and the "malloc" world.)
|
| + """
|
| + def __init__(self, unit_id, size):
|
| + self._unit_id = unit_id
|
| + self._size = size
|
| +
|
| + @property
|
| + def unit_id(self):
|
| + return self._unit_id
|
| +
|
| + @property
|
| + def size(self):
|
| + return self._size
|
| +
|
| +
|
| +class VMUnit(Unit):
|
| + """Represents a Unit for a memory region on virtual memory."""
|
| + def __init__(self, unit_id, committed, reserved, mmap, region,
|
| + pageframe=None, group_pfn_counts=None):
|
| + super(VMUnit, self).__init__(unit_id, committed)
|
| + self._reserved = reserved
|
| + self._mmap = mmap
|
| + self._region = region
|
| + self._pageframe = pageframe
|
| + self._group_pfn_counts = group_pfn_counts
|
| +
|
| + @property
|
| + def committed(self):
|
| + return self._size
|
| +
|
| + @property
|
| + def reserved(self):
|
| + return self._reserved
|
| +
|
| + @property
|
| + def mmap(self):
|
| + return self._mmap
|
| +
|
| + @property
|
| + def region(self):
|
| + return self._region
|
| +
|
| + @property
|
| + def pageframe(self):
|
| + return self._pageframe
|
| +
|
| + @property
|
| + def group_pfn_counts(self):
|
| + return self._group_pfn_counts
|
| +
|
| +
|
| +class MMapUnit(VMUnit):
|
| + """Represents a Unit for a mmap'ed region."""
|
| + def __init__(self, unit_id, committed, reserved, region, bucket_set,
|
| + pageframe=None, group_pfn_counts=None):
|
| + super(MMapUnit, self).__init__(unit_id, committed, reserved, True,
|
| + region, pageframe, group_pfn_counts)
|
| + self._bucket_set = bucket_set
|
| +
|
| + def __repr__(self):
|
| + return str(self.region)
|
| +
|
| + @property
|
| + def bucket_set(self):
|
| + return self._bucket_set
|
| +
|
| +
|
| +class UnhookedUnit(VMUnit):
|
| + """Represents a Unit for a non-mmap'ed memory region on virtual memory."""
|
| + def __init__(self, unit_id, committed, reserved, region,
|
| + pageframe=None, group_pfn_counts=None):
|
| + super(UnhookedUnit, self).__init__(unit_id, committed, reserved, False,
|
| + region, pageframe, group_pfn_counts)
|
| +
|
| + def __repr__(self):
|
| + return str(self.region)
|
| +
|
| +
|
| +class MallocUnit(Unit):
|
| + """Represents a Unit for a malloc'ed memory block."""
|
| + def __init__(self, unit_id, size, alloc_count, free_count, bucket):
|
| + super(MallocUnit, self).__init__(unit_id, size)
|
| + self._bucket = bucket
|
| + self._alloc_count = alloc_count
|
| + self._free_count = free_count
|
| +
|
| + def __repr__(self):
|
| + return str(self.bucket)
|
| +
|
| + @property
|
| + def bucket(self):
|
| + return self._bucket
|
| +
|
| + @property
|
| + def alloc_count(self):
|
| + return self._alloc_count
|
| +
|
| + @property
|
| + def free_count(self):
|
| + return self._free_count
|
| +
|
| +
|
| +class UnitSet(object):
|
| + """Represents an iterable set of Units."""
|
| + def __init__(self, world):
|
| + self._units = {}
|
| + self._world = world
|
| +
|
| + def __repr__(self):
|
| + return str(self._units)
|
| +
|
| + def __iter__(self):
|
| + for unit_id in sorted(self._units):
|
| + yield self._units[unit_id]
|
| +
|
| + def append(self, unit, overwrite=False):
|
| + if not overwrite and unit.unit_id in self._units:
|
| + LOGGER.error('The unit id=%s already exists.' % str(unit.unit_id))
|
| + self._units[unit.unit_id] = unit
|
| +
|
| +
|
| +class AbstractRule(object):
|
| + """An abstract class for rules to be matched with units."""
|
| + def __init__(self, dct):
|
| + self._name = dct['name']
|
| + self._hidden = dct.get('hidden', False)
|
| + self._subworlds = dct.get('subworlds', [])
|
| +
|
| + def match(self, unit):
|
| + raise NotImplementedError()
|
| +
|
| + @property
|
| + def name(self):
|
| + return self._name
|
| +
|
| + @property
|
| + def hidden(self):
|
| + return self._hidden
|
| +
|
| + def iter_subworld(self):
|
| + for subworld in self._subworlds:
|
| + yield subworld
|
| +
|
| +
|
| +class VMRule(AbstractRule):
|
| + """Represents a Rule to match with virtual memory regions."""
|
| + def __init__(self, dct):
|
| + super(VMRule, self).__init__(dct)
|
| + self._backtrace_function = dct.get('backtrace_function', None)
|
| + if self._backtrace_function:
|
| + self._backtrace_function = re.compile(self._backtrace_function)
|
| + self._backtrace_sourcefile = dct.get('backtrace_sourcefile', None)
|
| + if self._backtrace_sourcefile:
|
| + self._backtrace_sourcefile = re.compile(self._backtrace_sourcefile)
|
| + self._mmap = dct.get('mmap', None)
|
| + self._sharedwith = dct.get('sharedwith', [])
|
| + self._mapped_pathname = dct.get('mapped_pathname', None)
|
| + if self._mapped_pathname:
|
| + self._mapped_pathname = re.compile(self._mapped_pathname)
|
| + self._mapped_permission = dct.get('mapped_permission', None)
|
| + if self._mapped_permission:
|
| + self._mapped_permission = re.compile(self._mapped_permission)
|
| +
|
| + def __repr__(self):
|
| + result = cStringIO.StringIO()
|
| + result.write('{"%s"=>' % self._name)
|
| + attributes = []
|
| + attributes.append('mmap: %s' % self._mmap)
|
| + if self._backtrace_function:
|
| + attributes.append('backtrace_function: "%s"' %
|
| + self._backtrace_function.pattern)
|
| + if self._sharedwith:
|
| + attributes.append('sharedwith: "%s"' % self._sharedwith)
|
| + if self._mapped_pathname:
|
| + attributes.append('mapped_pathname: "%s"' % self._mapped_pathname.pattern)
|
| + if self._mapped_permission:
|
| + attributes.append('mapped_permission: "%s"' %
|
| + self._mapped_permission.pattern)
|
| + result.write('%s}' % ', '.join(attributes))
|
| + return result.getvalue()
|
| +
|
| + def match(self, unit):
|
| + if unit.mmap:
|
| + assert unit.region[0] == 'hooked'
|
| + bucket = unit.bucket_set.get(unit.region[1]['bucket_id'])
|
| + assert bucket
|
| + assert bucket.allocator_type == 'mmap'
|
| +
|
| + stackfunction = bucket.symbolized_joined_stackfunction
|
| + stacksourcefile = bucket.symbolized_joined_stacksourcefile
|
| +
|
| + # TODO(dmikurube): Support shared memory.
|
| + sharedwith = None
|
| +
|
| + if self._mmap == False: # (self._mmap == None) should go through.
|
| + return False
|
| + if (self._backtrace_function and
|
| + not self._backtrace_function.match(stackfunction)):
|
| + return False
|
| + if (self._backtrace_sourcefile and
|
| + not self._backtrace_sourcefile.match(stacksourcefile)):
|
| + return False
|
| + if (self._mapped_pathname and
|
| + not self._mapped_pathname.match(unit.region[1]['vma']['name'])):
|
| + return False
|
| + if (self._mapped_permission and
|
| + not self._mapped_permission.match(
|
| + unit.region[1]['vma']['readable'] +
|
| + unit.region[1]['vma']['writable'] +
|
| + unit.region[1]['vma']['executable'] +
|
| + unit.region[1]['vma']['private'])):
|
| + return False
|
| + if (self._sharedwith and
|
| + unit.pageframe and sharedwith not in self._sharedwith):
|
| + return False
|
| +
|
| + return True
|
| +
|
| + else:
|
| + assert unit.region[0] == 'unhooked'
|
| +
|
| + # TODO(dmikurube): Support shared memory.
|
| + sharedwith = None
|
| +
|
| + if self._mmap == True: # (self._mmap == None) should go through.
|
| + return False
|
| + if (self._mapped_pathname and
|
| + not self._mapped_pathname.match(unit.region[1]['vma']['name'])):
|
| + return False
|
| + if (self._mapped_permission and
|
| + not self._mapped_permission.match(
|
| + unit.region[1]['vma']['readable'] +
|
| + unit.region[1]['vma']['writable'] +
|
| + unit.region[1]['vma']['executable'] +
|
| + unit.region[1]['vma']['private'])):
|
| + return False
|
| + if (self._sharedwith and
|
| + unit.pageframe and sharedwith not in self._sharedwith):
|
| + return False
|
| +
|
| + return True
|
| +
|
| +
|
| +class MallocRule(AbstractRule):
|
| + """Represents a Rule to match with malloc'ed blocks."""
|
| + def __init__(self, dct):
|
| + super(MallocRule, self).__init__(dct)
|
| + self._backtrace_function = dct.get('backtrace_function', None)
|
| + if self._backtrace_function:
|
| + self._backtrace_function = re.compile(self._backtrace_function)
|
| + self._backtrace_sourcefile = dct.get('backtrace_sourcefile', None)
|
| + if self._backtrace_sourcefile:
|
| + self._backtrace_sourcefile = re.compile(self._backtrace_sourcefile)
|
| + self._typeinfo = dct.get('typeinfo', None)
|
| + if self._typeinfo:
|
| + self._typeinfo = re.compile(self._typeinfo)
|
| +
|
| + def __repr__(self):
|
| + result = cStringIO.StringIO()
|
| + result.write('{"%s"=>' % self._name)
|
| + attributes = []
|
| + if self._backtrace_function:
|
| + attributes.append('backtrace_function: "%s"' % self._backtrace_function)
|
| + if self._typeinfo:
|
| + attributes.append('typeinfo: "%s"' % self._typeinfo)
|
| + result.write('%s}' % ', '.join(attributes))
|
| + return result.getvalue()
|
| +
|
| + def match(self, unit):
|
| + assert unit.bucket.allocator_type == 'malloc'
|
| +
|
| + stackfunction = unit.bucket.symbolized_joined_stackfunction
|
| + stacksourcefile = unit.bucket.symbolized_joined_stacksourcefile
|
| + typeinfo = unit.bucket.symbolized_typeinfo
|
| + if typeinfo.startswith('0x'):
|
| + typeinfo = unit.bucket.typeinfo_name
|
| +
|
| + return ((not self._backtrace_function or
|
| + self._backtrace_function.match(stackfunction)) and
|
| + (not self._backtrace_sourcefile or
|
| + self._backtrace_sourcefile.match(stacksourcefile)) and
|
| + (not self._typeinfo or self._typeinfo.match(typeinfo)))
|
| +
|
| +
|
| +class NoBucketMallocRule(MallocRule):
|
| + """Represents a Rule that small ignorable units match with."""
|
| + def __init__(self):
|
| + super(NoBucketMallocRule, self).__init__({'name': 'tc-no-bucket'})
|
| + self._no_bucket = True
|
| +
|
| + @property
|
| + def no_bucket(self):
|
| + return self._no_bucket
|
| +
|
| +
|
| +class AbstractSorter(object):
|
| + """An abstract class for classifying Units with a set of Rules."""
|
| + def __init__(self, dct):
|
| + self._type = 'sorter'
|
| + self._version = dct['version']
|
| + self._world = dct['world']
|
| + self._name = dct['name']
|
| + self._order = dct['order']
|
| +
|
| + self._rules = []
|
| + for rule in dct['rules']:
|
| + if dct['world'] == 'vm':
|
| + self._rules.append(VMRule(rule))
|
| + elif dct['world'] == 'malloc':
|
| + self._rules.append(MallocRule(rule))
|
| + else:
|
| + LOGGER.error('Unknown sorter world type')
|
| +
|
| + def __repr__(self):
|
| + result = cStringIO.StringIO()
|
| + result.write('world=%s' % self._world)
|
| + result.write('order=%s' % self._order)
|
| + result.write('rules:')
|
| + for rule in self._rules:
|
| + result.write(' %s' % rule)
|
| + return result.getvalue()
|
| +
|
| + @staticmethod
|
| + def load(filename):
|
| + with open(filename) as sorter_f:
|
| + sorter_dict = json.load(sorter_f)
|
| + if sorter_dict['world'] == 'vm':
|
| + return VMSorter(sorter_dict)
|
| + elif sorter_dict['world'] == 'malloc':
|
| + return MallocSorter(sorter_dict)
|
| + else:
|
| + LOGGER.error('Unknown sorter world type')
|
| + return None
|
| +
|
| + @property
|
| + def world(self):
|
| + return self._world
|
| +
|
| + @property
|
| + def name(self):
|
| + return self._name
|
| +
|
| + def find(self, unit):
|
| + raise NotImplementedError()
|
| +
|
| + def find_rule(self, name):
|
| + """Finds a rule whose name is |name|. """
|
| + for rule in self._rules:
|
| + if rule.name == name:
|
| + return rule
|
| + return None
|
| +
|
| +
|
| +class VMSorter(AbstractSorter):
|
| + """Represents a Sorter for memory regions on virtual memory."""
|
| + def __init__(self, dct):
|
| + assert dct['world'] == 'vm'
|
| + super(VMSorter, self).__init__(dct)
|
| +
|
| + def find(self, unit):
|
| + for rule in self._rules:
|
| + if rule.match(unit):
|
| + return rule
|
| + assert False
|
| +
|
| +
|
| +class MallocSorter(AbstractSorter):
|
| + """Represents a Sorter for malloc'ed blocks."""
|
| + def __init__(self, dct):
|
| + assert dct['world'] == 'malloc'
|
| + super(MallocSorter, self).__init__(dct)
|
| + self._no_bucket_rule = NoBucketMallocRule()
|
| +
|
| + def find(self, unit):
|
| + if not unit.bucket:
|
| + return self._no_bucket_rule
|
| + assert unit.bucket.allocator_type == 'malloc'
|
| +
|
| + if unit.bucket.component_cache:
|
| + return unit.bucket.component_cache
|
| +
|
| + for rule in self._rules:
|
| + if rule.match(unit):
|
| + unit.bucket.component_cache = rule
|
| + return rule
|
| + assert False
|
| +
|
| +
|
| +class SorterSet(object):
|
| + """Represents an iterable set of Sorters."""
|
| + def __init__(self, additional=None, default=None):
|
| + if not additional:
|
| + additional = []
|
| + if not default:
|
| + default = DEFAULT_SORTERS
|
| + self._sorters = {}
|
| + for filename in default + additional:
|
| + sorter = AbstractSorter.load(filename)
|
| + if sorter.world not in self._sorters:
|
| + self._sorters[sorter.world] = []
|
| + self._sorters[sorter.world].append(sorter)
|
| +
|
| + def __repr__(self):
|
| + result = cStringIO.StringIO()
|
| + result.write(self._sorters)
|
| + return result.getvalue()
|
| +
|
| + def __iter__(self):
|
| + for sorters in self._sorters.itervalues():
|
| + for sorter in sorters:
|
| + yield sorter
|
| +
|
| + def iter_world(self, world):
|
| + for sorter in self._sorters.get(world, []):
|
| + yield sorter
|
|
|