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.
11 from lib
.ordered_dict
import OrderedDict
14 LOGGER
= logging
.getLogger('dmprof')
16 BASE_PATH
= os
.path
.dirname(os
.path
.dirname(os
.path
.abspath(__file__
)))
19 os
.path
.join(BASE_PATH
, 'sorters', 'malloc.browser-module.json'),
20 os
.path
.join(BASE_PATH
, 'sorters', 'malloc.renderer-module.json'),
21 os
.path
.join(BASE_PATH
, 'sorters', 'malloc.type.json'),
22 os
.path
.join(BASE_PATH
, 'sorters', 'malloc.WebCore.json'),
23 os
.path
.join(BASE_PATH
, 'sorters', 'vm.Android-specific.json'),
24 os
.path
.join(BASE_PATH
, 'sorters', 'vm.base.json'),
25 os
.path
.join(BASE_PATH
, 'sorters', 'vm.GPU.json'),
26 os
.path
.join(BASE_PATH
, 'sorters', 'vm.sharing.json'),
27 os
.path
.join(BASE_PATH
, 'sorters', 'vm.Skia.json'),
28 os
.path
.join(BASE_PATH
, 'sorters', 'vm.V8.json'),
31 DEFAULT_TEMPLATES
= os
.path
.join(BASE_PATH
, 'templates.json')
35 """Represents a minimum unit of memory usage categorization.
37 It is supposed to be inherited for some different spaces like the entire
38 virtual memory and malloc arena. Such different spaces are called "worlds"
39 in dmprof. (For example, the "vm" world and the "malloc" world.)
41 def __init__(self
, unit_id
, size
):
42 self
._unit
_id
= unit_id
55 """Represents a Unit for a memory region on virtual memory."""
56 def __init__(self
, unit_id
, committed
, reserved
, mmap
, region
,
57 pageframe
=None, group_pfn_counts
=None):
58 super(VMUnit
, self
).__init
__(unit_id
, committed
)
59 self
._reserved
= reserved
62 self
._pageframe
= pageframe
63 self
._group
_pfn
_counts
= group_pfn_counts
83 return self
._pageframe
86 def group_pfn_counts(self
):
87 return self
._group
_pfn
_counts
90 class MMapUnit(VMUnit
):
91 """Represents a Unit for a mmap'ed region."""
92 def __init__(self
, unit_id
, committed
, reserved
, region
, bucket_set
,
93 pageframe
=None, group_pfn_counts
=None):
94 super(MMapUnit
, self
).__init
__(unit_id
, committed
, reserved
, True,
95 region
, pageframe
, group_pfn_counts
)
96 self
._bucket
_set
= bucket_set
99 return str(self
.region
)
102 def bucket_set(self
):
103 return self
._bucket
_set
106 class UnhookedUnit(VMUnit
):
107 """Represents a Unit for a non-mmap'ed memory region on virtual memory."""
108 def __init__(self
, unit_id
, committed
, reserved
, region
,
109 pageframe
=None, group_pfn_counts
=None):
110 super(UnhookedUnit
, self
).__init
__(unit_id
, committed
, reserved
, False,
111 region
, pageframe
, group_pfn_counts
)
114 return str(self
.region
)
117 class MallocUnit(Unit
):
118 """Represents a Unit for a malloc'ed memory block."""
119 def __init__(self
, unit_id
, size
, alloc_count
, free_count
, bucket
):
120 super(MallocUnit
, self
).__init
__(unit_id
, size
)
121 self
._bucket
= bucket
122 self
._alloc
_count
= alloc_count
123 self
._free
_count
= free_count
126 return str(self
.bucket
)
133 def alloc_count(self
):
134 return self
._alloc
_count
137 def free_count(self
):
138 return self
._free
_count
141 class UnitSet(object):
142 """Represents an iterable set of Units."""
143 def __init__(self
, world
):
148 return str(self
._units
)
151 for unit_id
in sorted(self
._units
):
152 yield self
._units
[unit_id
]
154 def append(self
, unit
, overwrite
=False):
155 if not overwrite
and unit
.unit_id
in self
._units
:
156 LOGGER
.error('The unit id=%s already exists.' % str(unit
.unit_id
))
157 self
._units
[unit
.unit_id
] = unit
160 class AbstractRule(object):
161 """An abstract class for rules to be matched with units."""
162 def __init__(self
, dct
):
163 self
._name
= dct
['name']
164 self
._hidden
= dct
.get('hidden', False)
165 self
._subs
= dct
.get('subs', [])
167 def match(self
, unit
):
168 raise NotImplementedError()
179 for sub
in self
._subs
:
183 class VMRule(AbstractRule
):
184 """Represents a Rule to match with virtual memory regions."""
185 def __init__(self
, dct
):
186 super(VMRule
, self
).__init
__(dct
)
187 self
._backtrace
_function
= dct
.get('backtrace_function', None)
188 if self
._backtrace
_function
:
189 self
._backtrace
_function
= re
.compile(self
._backtrace
_function
)
190 self
._backtrace
_sourcefile
= dct
.get('backtrace_sourcefile', None)
191 if self
._backtrace
_sourcefile
:
192 self
._backtrace
_sourcefile
= re
.compile(self
._backtrace
_sourcefile
)
193 self
._mmap
= dct
.get('mmap', None)
194 self
._sharedwith
= dct
.get('sharedwith', [])
195 self
._mapped
_pathname
= dct
.get('mapped_pathname', None)
196 if self
._mapped
_pathname
:
197 self
._mapped
_pathname
= re
.compile(self
._mapped
_pathname
)
198 self
._mapped
_permission
= dct
.get('mapped_permission', None)
199 if self
._mapped
_permission
:
200 self
._mapped
_permission
= re
.compile(self
._mapped
_permission
)
203 result
= cStringIO
.StringIO()
204 result
.write('%s: ' % self
._name
)
206 attributes
.append('mmap: %s' % self
._mmap
)
207 if self
._backtrace
_function
:
208 attributes
.append('backtrace_function: "%s"' %
209 self
._backtrace
_function
.pattern
)
211 attributes
.append('sharedwith: "%s"' % self
._sharedwith
)
212 if self
._mapped
_pathname
:
213 attributes
.append('mapped_pathname: "%s"' % self
._mapped
_pathname
.pattern
)
214 if self
._mapped
_permission
:
215 attributes
.append('mapped_permission: "%s"' %
216 self
._mapped
_permission
.pattern
)
217 result
.write('{ %s }' % ', '.join(attributes
))
218 return result
.getvalue()
220 def match(self
, unit
):
222 assert unit
.region
[0] == 'hooked'
223 bucket
= unit
.bucket_set
.get(unit
.region
[1]['bucket_id'])
225 assert bucket
.allocator_type
== 'mmap'
227 stackfunction
= bucket
.symbolized_joined_stackfunction
228 stacksourcefile
= bucket
.symbolized_joined_stacksourcefile
230 # TODO(dmikurube): Support shared memory.
233 if self
._mmap
== False: # (self._mmap == None) should go through.
235 if (self
._backtrace
_function
and
236 not self
._backtrace
_function
.match(stackfunction
)):
238 if (self
._backtrace
_sourcefile
and
239 not self
._backtrace
_sourcefile
.match(stacksourcefile
)):
241 if (self
._mapped
_pathname
and
242 not self
._mapped
_pathname
.match(unit
.region
[1]['vma']['name'])):
244 if (self
._mapped
_permission
and
245 not self
._mapped
_permission
.match(
246 unit
.region
[1]['vma']['readable'] +
247 unit
.region
[1]['vma']['writable'] +
248 unit
.region
[1]['vma']['executable'] +
249 unit
.region
[1]['vma']['private'])):
251 if (self
._sharedwith
and
252 unit
.pageframe
and sharedwith
not in self
._sharedwith
):
258 assert unit
.region
[0] == 'unhooked'
260 # TODO(dmikurube): Support shared memory.
263 if self
._mmap
== True: # (self._mmap == None) should go through.
265 if (self
._mapped
_pathname
and
266 not self
._mapped
_pathname
.match(unit
.region
[1]['vma']['name'])):
268 if (self
._mapped
_permission
and
269 not self
._mapped
_permission
.match(
270 unit
.region
[1]['vma']['readable'] +
271 unit
.region
[1]['vma']['writable'] +
272 unit
.region
[1]['vma']['executable'] +
273 unit
.region
[1]['vma']['private'])):
275 if (self
._sharedwith
and
276 unit
.pageframe
and sharedwith
not in self
._sharedwith
):
282 class MallocRule(AbstractRule
):
283 """Represents a Rule to match with malloc'ed blocks."""
284 def __init__(self
, dct
):
285 super(MallocRule
, self
).__init
__(dct
)
286 self
._backtrace
_function
= dct
.get('backtrace_function', None)
287 if self
._backtrace
_function
:
288 self
._backtrace
_function
= re
.compile(self
._backtrace
_function
)
289 self
._backtrace
_sourcefile
= dct
.get('backtrace_sourcefile', None)
290 if self
._backtrace
_sourcefile
:
291 self
._backtrace
_sourcefile
= re
.compile(self
._backtrace
_sourcefile
)
292 self
._typeinfo
= dct
.get('typeinfo', None)
294 self
._typeinfo
= re
.compile(self
._typeinfo
)
297 result
= cStringIO
.StringIO()
298 result
.write('%s: ' % self
._name
)
300 if self
._backtrace
_function
:
301 attributes
.append('backtrace_function: "%s"' %
302 self
._backtrace
_function
.pattern
)
304 attributes
.append('typeinfo: "%s"' % self
._typeinfo
.pattern
)
305 result
.write('{ %s }' % ', '.join(attributes
))
306 return result
.getvalue()
308 def match(self
, unit
):
309 assert unit
.bucket
.allocator_type
== 'malloc'
311 stackfunction
= unit
.bucket
.symbolized_joined_stackfunction
312 stacksourcefile
= unit
.bucket
.symbolized_joined_stacksourcefile
313 typeinfo
= unit
.bucket
.symbolized_typeinfo
314 if typeinfo
.startswith('0x'):
315 typeinfo
= unit
.bucket
.typeinfo_name
317 return ((not self
._backtrace
_function
or
318 self
._backtrace
_function
.match(stackfunction
)) and
319 (not self
._backtrace
_sourcefile
or
320 self
._backtrace
_sourcefile
.match(stacksourcefile
)) and
321 (not self
._typeinfo
or self
._typeinfo
.match(typeinfo
)))
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
._root
= dct
.get('root', False)
332 self
._order
= dct
['order']
335 for rule
in dct
['rules']:
336 if dct
['world'] == 'vm':
337 self
._rules
.append(VMRule(rule
))
338 elif dct
['world'] == 'malloc':
339 self
._rules
.append(MallocRule(rule
))
341 LOGGER
.error('Unknown sorter world type')
344 result
= cStringIO
.StringIO()
345 print >> result
, '%s' % self
._name
346 print >> result
, 'world=%s' % self
._world
347 print >> result
, 'name=%s' % self
._name
348 print >> result
, 'order=%s' % self
._order
349 print >> result
, 'rules:'
350 for rule
in self
._rules
:
351 print >> result
, ' %s' % rule
352 return result
.getvalue()
356 with
open(filename
) as sorter_f
:
357 sorter_dict
= json
.load(sorter_f
, object_pairs_hook
=OrderedDict
)
358 if sorter_dict
['world'] == 'vm':
359 return VMSorter(sorter_dict
)
360 elif sorter_dict
['world'] == 'malloc':
361 return MallocSorter(sorter_dict
)
363 LOGGER
.error('Unknown sorter world type')
379 for rule
in self
._rules
:
382 def find(self
, unit
):
383 raise NotImplementedError()
385 def find_rule(self
, name
):
386 """Finds a rule whose name is |name|. """
387 for rule
in self
._rules
:
388 if rule
.name
== name
:
393 class VMSorter(AbstractSorter
):
394 """Represents a Sorter for memory regions on virtual memory."""
395 def __init__(self
, dct
):
396 assert dct
['world'] == 'vm'
397 super(VMSorter
, self
).__init
__(dct
)
399 def find(self
, unit
):
400 for rule
in self
._rules
:
406 class MallocSorter(AbstractSorter
):
407 """Represents a Sorter for malloc'ed blocks."""
408 def __init__(self
, dct
):
409 assert dct
['world'] == 'malloc'
410 super(MallocSorter
, self
).__init
__(dct
)
412 def find(self
, unit
):
415 assert unit
.bucket
.allocator_type
== 'malloc'
417 # TODO(dmikurube): Utilize component_cache again, or remove it.
419 for rule
in self
._rules
:
425 class SorterTemplates(object):
426 """Represents a template for sorters."""
427 def __init__(self
, dct
):
435 with
open(filename
) as templates_f
:
436 templates_dict
= json
.load(templates_f
, object_pairs_hook
=OrderedDict
)
437 return SorterTemplates(templates_dict
)
440 class SorterSet(object):
441 """Represents an iterable set of Sorters."""
442 def __init__(self
, additional
=None, default
=None):
446 default
= DEFAULT_SORTERS
448 LOGGER
.info('Loading sorters.')
449 for filename
in default
+ additional
:
450 LOGGER
.info(' Loading a sorter "%s".' % filename
)
451 sorter
= AbstractSorter
.load(filename
)
452 if sorter
.world
not in self
._sorters
:
453 self
._sorters
[sorter
.world
] = []
454 self
._sorters
[sorter
.world
].append(sorter
)
455 self
._templates
= SorterTemplates
.load(DEFAULT_TEMPLATES
)
458 result
= cStringIO
.StringIO()
459 for world
, sorters
in self
._sorters
.iteritems():
460 for sorter
in sorters
:
461 print >> result
, '%s: %s' % (world
, sorter
)
462 return result
.getvalue()
465 for sorters
in self
._sorters
.itervalues():
466 for sorter
in sorters
:
469 def iter_world(self
, world
):
470 for sorter
in self
._sorters
.get(world
, []):
475 return self
._templates