Index: tools/telemetry/telemetry/timeline/memory_dump_event.py |
diff --git a/tools/telemetry/telemetry/timeline/memory_dump_event.py b/tools/telemetry/telemetry/timeline/memory_dump_event.py |
deleted file mode 100644 |
index bcd2ce26fc25ed9c9dc4784211057ecd8d53fce8..0000000000000000000000000000000000000000 |
--- a/tools/telemetry/telemetry/timeline/memory_dump_event.py |
+++ /dev/null |
@@ -1,336 +0,0 @@ |
-# Copyright 2015 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 posixpath |
-import re |
- |
-from telemetry.timeline import event as timeline_event |
- |
- |
-class MmapCategory(object): |
- _DEFAULT_CATEGORY = None |
- |
- def __init__(self, name, file_pattern, children=None): |
- """A (sub)category for classifying memory maps. |
- |
- Args: |
- name: A string to identify the category. |
- file_pattern: A regex pattern, the category will aggregate memory usage |
- for all mapped files matching this pattern. |
- children: A list of MmapCategory objects, used to sub-categorize memory |
- usage. |
- """ |
- self.name = name |
- self._file_pattern = re.compile(file_pattern) if file_pattern else None |
- self._children = list(children) if children else None |
- |
- @classmethod |
- def DefaultCategory(cls): |
- """An implicit 'Others' match-all category with no children.""" |
- if cls._DEFAULT_CATEGORY is None: |
- cls._DEFAULT_CATEGORY = cls('Others', None) |
- return cls._DEFAULT_CATEGORY |
- |
- def Match(self, mapped_file): |
- """Test whether a mapped file matches this category.""" |
- return (self._file_pattern is None |
- or bool(self._file_pattern.search(mapped_file))) |
- |
- def GetMatchingChild(self, mapped_file): |
- """Get the first matching sub-category for a given mapped file. |
- |
- Returns None if the category has no children, or the DefaultCategory if |
- it does have children but none of them match. |
- """ |
- if not self._children: |
- return None |
- for child in self._children: |
- if child.Match(mapped_file): |
- return child |
- return type(self).DefaultCategory() |
- |
- |
-ROOT_CATEGORY = MmapCategory('/', None, [ |
- MmapCategory('Android', r'^\/dev\/ashmem(?!\/libc malloc)', [ |
- MmapCategory('Java runtime', r'^\/dev\/ashmem\/dalvik-', [ |
- MmapCategory('Spaces', r'\/dalvik-(alloc|main|large' |
- r' object|non moving|zygote) space', [ |
- MmapCategory('Normal', r'\/dalvik-(alloc|main)'), |
- MmapCategory('Large', r'\/dalvik-large object'), |
- MmapCategory('Zygote', r'\/dalvik-zygote'), |
- MmapCategory('Non-moving', r'\/dalvik-non moving') |
- ]), |
- MmapCategory('Linear Alloc', r'\/dalvik-LinearAlloc'), |
- MmapCategory('Indirect Reference Table', r'\/dalvik-indirect.ref'), |
- MmapCategory('Cache', r'\/dalvik-jit-code-cache'), |
- MmapCategory('Accounting', None) |
- ]), |
- MmapCategory('Cursor', r'\/CursorWindow'), |
- MmapCategory('Ashmem', None) |
- ]), |
- MmapCategory('Native heap', |
- r'^((\[heap\])|(\[anon:)|(\/dev\/ashmem\/libc malloc)|$)'), |
- MmapCategory('Stack', r'^\[stack'), |
- MmapCategory('Files', |
- r'\.((((so)|(jar)|(apk)|(ttf)|(odex)|(oat)|(arg))$)|(dex))', [ |
- MmapCategory('so', r'\.so$'), |
- MmapCategory('jar', r'\.jar$'), |
- MmapCategory('apk', r'\.apk$'), |
- MmapCategory('ttf', r'\.ttf$'), |
- MmapCategory('dex', r'\.((dex)|(odex$))'), |
- MmapCategory('oat', r'\.oat$'), |
- MmapCategory('art', r'\.art$'), |
- ]), |
- MmapCategory('Devices', r'(^\/dev\/)|(anon_inode:dmabuf)', [ |
- MmapCategory('GPU', r'\/((nv)|(mali)|(kgsl))'), |
- MmapCategory('DMA', r'anon_inode:dmabuf'), |
- ]), |
- MmapCategory('Discounted tracing overhead', |
- r'\[discounted tracing overhead\]') |
-]) |
- |
- |
-# Map long descriptive attribute names, as understood by MemoryBucket.GetValue, |
-# to the short keys used by events in raw json traces. |
-BUCKET_ATTRS = { |
- 'proportional_resident': 'pss', |
- 'private_dirty_resident': 'pd', |
- 'private_clean_resident': 'pc', |
- 'shared_dirty_resident': 'sd', |
- 'shared_clean_resident': 'sc', |
- 'swapped': 'sw'} |
- |
- |
-# Map of {memory_key: (category_path, discount_tracing), ...}. |
-# When discount_tracing is True, we have to discount the resident_size of the |
-# tracing allocator to get the correct value for that key. |
-MMAPS_METRICS = { |
- 'mmaps_overall_pss': ('/.proportional_resident', True), |
- 'mmaps_private_dirty' : ('/.private_dirty_resident', True), |
- 'mmaps_java_heap': ('/Android/Java runtime/Spaces.proportional_resident', |
- False), |
- 'mmaps_ashmem': ('/Android/Ashmem.proportional_resident', False), |
- 'mmaps_native_heap': ('/Native heap.proportional_resident', True)} |
- |
- |
-class MemoryBucket(object): |
- """Simple object to hold and aggregate memory values.""" |
- def __init__(self): |
- self._bucket = dict.fromkeys(BUCKET_ATTRS.iterkeys(), 0) |
- |
- def __repr__(self): |
- values = ', '.join('%s=%d' % (src_key, self._bucket[dst_key]) |
- for dst_key, src_key |
- in sorted(BUCKET_ATTRS.iteritems())) |
- return '%s[%s]' % (type(self).__name__, values) |
- |
- def AddRegion(self, byte_stats): |
- for dst_key, src_key in BUCKET_ATTRS.iteritems(): |
- self._bucket[dst_key] += int(byte_stats.get(src_key, '0'), 16) |
- |
- def GetValue(self, name): |
- return self._bucket[name] |
- |
- |
-class ProcessMemoryDumpEvent(timeline_event.TimelineEvent): |
- """A memory dump event belonging to a single timeline.Process object. |
- |
- It's a subclass of telemetry's TimelineEvent so it can be included in |
- the stream of events contained in timeline.model objects, and have its |
- timing correlated with that of other events in the model. |
- |
- Args: |
- process: The Process object associated with the memory dump. |
- dump_events: A list of dump events of the process with the same dump id. |
- |
- Properties: |
- dump_id: A string to identify events belonging to the same global dump. |
- process: The timeline.Process object that owns this memory dump event. |
- has_mmaps: True if the memory dump has mmaps information. If False then |
- GetMemoryUsage will report all zeros. |
- """ |
- def __init__(self, process, dump_events): |
- assert dump_events |
- |
- start_time = min(event['ts'] for event in dump_events) / 1000.0 |
- duration = max(event['ts'] for event in dump_events) / 1000.0 - start_time |
- super(ProcessMemoryDumpEvent, self).__init__('memory', 'memory_dump', |
- start_time, duration) |
- |
- self.process = process |
- self.dump_id = dump_events[0]['id'] |
- |
- allocator_dumps = {} |
- vm_regions = [] |
- for event in dump_events: |
- assert (event['ph'] == 'v' and self.process.pid == event['pid'] and |
- self.dump_id == event['id']) |
- try: |
- allocator_dumps.update(event['args']['dumps']['allocators']) |
- except KeyError: |
- pass # It's ok if any of those keys are not present. |
- try: |
- value = event['args']['dumps']['process_mmaps']['vm_regions'] |
- assert not vm_regions |
- vm_regions = value |
- except KeyError: |
- pass # It's ok if any of those keys are not present. |
- |
- self._allocators = {} |
- parent_path = '' |
- parent_has_size = False |
- for allocator_name, size_values in sorted(allocator_dumps.iteritems()): |
- if ((allocator_name.startswith(parent_path) and parent_has_size) or |
- allocator_name.startswith('global/')): |
- continue |
- parent_path = allocator_name + '/' |
- parent_has_size = 'size' in size_values['attrs'] |
- name_parts = allocator_name.split('/') |
- allocator_name = name_parts[0] |
- # For 'gpu/android_memtrack/*' we want to keep track of individual |
- # components. E.g. 'gpu/android_memtrack/gl' will be stored as |
- # 'android_memtrack_gl' in the allocators dict. |
- if (len(name_parts) == 3 and allocator_name == 'gpu' and |
- name_parts[1] == 'android_memtrack'): |
- allocator_name = '_'.join(name_parts[1:3]) |
- allocator = self._allocators.setdefault(allocator_name, {}) |
- for size_key, size_value in size_values['attrs'].iteritems(): |
- if size_value['units'] == 'bytes': |
- allocator[size_key] = (allocator.get(size_key, 0) |
- + int(size_value['value'], 16)) |
- # we need to discount tracing from malloc size. |
- try: |
- self._allocators['malloc']['size'] -= self._allocators['tracing']['size'] |
- except KeyError: |
- pass # It's ok if any of those keys are not present. |
- |
- self.has_mmaps = bool(vm_regions) |
- self._buckets = {} |
- for vm_region in vm_regions: |
- self._AddRegion(vm_region) |
- |
- @property |
- def process_name(self): |
- return self.process.name |
- |
- def _AddRegion(self, vm_region): |
- path = '' |
- category = ROOT_CATEGORY |
- while category: |
- path = posixpath.join(path, category.name) |
- self.GetMemoryBucket(path).AddRegion(vm_region['bs']) |
- mapped_file = vm_region['mf'] |
- category = category.GetMatchingChild(mapped_file) |
- |
- def __repr__(self): |
- values = ['pid=%d' % self.process.pid] |
- for key, value in sorted(self.GetMemoryUsage().iteritems()): |
- values.append('%s=%d' % (key, value)) |
- values = ', '.join(values) |
- return '%s[%s]' % (type(self).__name__, values) |
- |
- def GetMemoryBucket(self, path): |
- """Return the MemoryBucket associated with a category path. |
- |
- An empty bucket will be created if the path does not already exist. |
- |
- path: A string with path in the classification tree, e.g. |
- '/Android/Java runtime/Cache'. Note: no trailing slash, except for |
- the root path '/'. |
- """ |
- if not path in self._buckets: |
- self._buckets[path] = MemoryBucket() |
- return self._buckets[path] |
- |
- def GetMemoryValue(self, category_path, discount_tracing=False): |
- """Return a specific value from within a MemoryBucket. |
- |
- category_path: A string composed of a path in the classification tree, |
- followed by a '.', followed by a specific bucket value, e.g. |
- '/Android/Java runtime/Cache.private_dirty_resident'. |
- discount_tracing: A boolean indicating whether the returned value should |
- be discounted by the resident size of the tracing allocator. |
- """ |
- path, name = category_path.rsplit('.', 1) |
- value = self.GetMemoryBucket(path).GetValue(name) |
- if discount_tracing and 'tracing' in self._allocators: |
- value -= self._allocators['tracing'].get('resident_size', 0) |
- return value |
- |
- def GetMemoryUsage(self): |
- """Get a dictionary with the memory usage of this process.""" |
- usage = {} |
- for name, values in self._allocators.iteritems(): |
- # If you wish to track more attributes here, make sure they are correctly |
- # calculated by the ProcessMemoryDumpEvent method. All dumps whose parent |
- # has "size" attribute are ignored to avoid double counting. So, the |
- # other attributes are totals of only top level dumps. |
- if 'size' in values: |
- usage['allocator_%s' % name] = values['size'] |
- if 'allocated_objects_size' in values: |
- usage['allocated_objects_%s' % name] = values['allocated_objects_size'] |
- if 'memtrack_pss' in values: |
- usage[name] = values['memtrack_pss'] |
- if self.has_mmaps: |
- usage.update((key, self.GetMemoryValue(*value)) |
- for key, value in MMAPS_METRICS.iteritems()) |
- return usage |
- |
- |
-class GlobalMemoryDump(object): |
- """Object to aggregate individual process dumps with the same dump id. |
- |
- Args: |
- process_dumps: A sequence of ProcessMemoryDumpEvent objects, all sharing |
- the same global dump id. |
- |
- Attributes: |
- dump_id: A string identifying this dump. |
- has_mmaps: True if the memory dump has mmaps information. If False then |
- GetMemoryUsage will report all zeros. |
- """ |
- def __init__(self, process_dumps): |
- assert process_dumps |
- # Keep dumps sorted in chronological order. |
- self._process_dumps = sorted(process_dumps, key=lambda dump: dump.start) |
- |
- # All process dump events should have the same dump id. |
- dump_ids = set(dump.dump_id for dump in self._process_dumps) |
- assert len(dump_ids) == 1 |
- self.dump_id = dump_ids.pop() |
- |
- # Either all processes have mmaps or none of them do. |
- have_mmaps = set(dump.has_mmaps for dump in self._process_dumps) |
- assert len(have_mmaps) == 1 |
- self.has_mmaps = have_mmaps.pop() |
- |
- @property |
- def start(self): |
- return self._process_dumps[0].start |
- |
- @property |
- def end(self): |
- return max(dump.end for dump in self._process_dumps) |
- |
- @property |
- def duration(self): |
- return self.end - self.start |
- |
- def IterProcessMemoryDumps(self): |
- return iter(self._process_dumps) |
- |
- def __repr__(self): |
- values = ['id=%s' % self.dump_id] |
- for key, value in sorted(self.GetMemoryUsage().iteritems()): |
- values.append('%s=%d' % (key, value)) |
- values = ', '.join(values) |
- return '%s[%s]' % (type(self).__name__, values) |
- |
- def GetMemoryUsage(self): |
- """Get the aggregated memory usage over all processes in this dump.""" |
- result = {} |
- for dump in self._process_dumps: |
- for key, value in dump.GetMemoryUsage().iteritems(): |
- result[key] = result.get(key, 0) + value |
- return result |