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