| Index: tools/deep_memory_profiler/dmprof.py
|
| diff --git a/tools/deep_memory_profiler/dmprof.py b/tools/deep_memory_profiler/dmprof.py
|
| deleted file mode 100755
|
| index e9c642c80d168cb82a2b455cabe0aa0efb16ccf0..0000000000000000000000000000000000000000
|
| --- a/tools/deep_memory_profiler/dmprof.py
|
| +++ /dev/null
|
| @@ -1,767 +0,0 @@
|
| -#!/usr/bin/env python
|
| -# Copyright (c) 2012 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.
|
| -
|
| -"""The deep heap profiler script for Chrome."""
|
| -
|
| -from datetime import datetime
|
| -import json
|
| -import os
|
| -import re
|
| -import subprocess
|
| -import sys
|
| -import tempfile
|
| -
|
| -BUCKET_ID = 5
|
| -VIRTUAL = 0
|
| -COMMITTED = 1
|
| -ALLOC_COUNT = 2
|
| -FREE_COUNT = 3
|
| -NULL_REGEX = re.compile('')
|
| -PPROF_PATH = os.path.join(os.path.dirname(__file__),
|
| - os.pardir,
|
| - os.pardir,
|
| - 'third_party',
|
| - 'tcmalloc',
|
| - 'chromium',
|
| - 'src',
|
| - 'pprof')
|
| -
|
| -# Heap Profile Dump versions
|
| -
|
| -# DUMP_DEEP_1 DOES NOT distinct mmap regions and malloc chunks.
|
| -# Their stacktraces DO contain mmap* or tc-* at their tops.
|
| -# They should be processed by POLICY_DEEP_1.
|
| -DUMP_DEEP_1 = 'DUMP_DEEP_1'
|
| -
|
| -# DUMP_DEEP_2 DOES distinct mmap regions and malloc chunks.
|
| -# Their stacktraces still DO contain mmap* or tc-*.
|
| -# They should be processed by POLICY_DEEP_1.
|
| -DUMP_DEEP_2 = 'DUMP_DEEP_2'
|
| -
|
| -# DUMP_DEEP_3 DOES distinct mmap regions and malloc chunks.
|
| -# Their stacktraces DO NOT contain mmap* or tc-*.
|
| -# They should be processed by POLICY_DEEP_2.
|
| -DUMP_DEEP_3 = 'DUMP_DEEP_3'
|
| -
|
| -# Heap Profile Policy versions
|
| -
|
| -# POLICY_DEEP_1 DOES NOT include allocation_type columns.
|
| -# mmap regions are distincted w/ mmap frames in the pattern column.
|
| -POLICY_DEEP_1 = 'POLICY_DEEP_1'
|
| -
|
| -# POLICY_DEEP_2 DOES include allocation_type columns.
|
| -# mmap regions are distincted w/ the allocation_type column.
|
| -POLICY_DEEP_2 = 'POLICY_DEEP_2'
|
| -
|
| -# TODO(dmikurube): Avoid global variables.
|
| -address_symbol_dict = {}
|
| -appeared_addresses = set()
|
| -components = []
|
| -
|
| -
|
| -class Policy(object):
|
| -
|
| - def __init__(self, name, mmap, pattern):
|
| - self.name = name
|
| - self.mmap = mmap
|
| - self.condition = re.compile(pattern + r'\Z')
|
| -
|
| -
|
| -def get_component(policy_list, bucket, mmap):
|
| - """Returns a component name which a given bucket belongs to.
|
| -
|
| - Args:
|
| - policy_list: A list containing Policy objects. (Parsed policy data by
|
| - parse_policy.)
|
| - bucket: A Bucket object to be searched for.
|
| - mmap: True if searching for a mmap region.
|
| -
|
| - Returns:
|
| - A string representing a component name.
|
| - """
|
| - if not bucket:
|
| - return 'no-bucket'
|
| - if bucket.component:
|
| - return bucket.component
|
| -
|
| - stacktrace = ''.join(
|
| - address_symbol_dict[a] + ' ' for a in bucket.stacktrace).strip()
|
| -
|
| - for policy in policy_list:
|
| - if mmap == policy.mmap and policy.condition.match(stacktrace):
|
| - bucket.component = policy.name
|
| - return policy.name
|
| -
|
| - assert False
|
| -
|
| -
|
| -class Bucket(object):
|
| -
|
| - def __init__(self, stacktrace):
|
| - self.stacktrace = stacktrace
|
| - self.component = ''
|
| -
|
| -
|
| -class Log(object):
|
| -
|
| - """A class representing one dumped log data."""
|
| - def __init__(self, log_path, buckets):
|
| - self.log_path = log_path
|
| - with open(self.log_path, mode='r') as log_f:
|
| - self.log_lines = log_f.readlines()
|
| - self.log_version = ''
|
| - sys.stderr.write('parsing a log file:%s\n' % log_path)
|
| - self.mmap_stacktrace_lines = []
|
| - self.malloc_stacktrace_lines = []
|
| - self.counters = {}
|
| - self.log_time = os.stat(self.log_path).st_mtime
|
| - self.parse_log(buckets)
|
| -
|
| - @staticmethod
|
| - def dump_stacktrace_lines(stacktrace_lines, buckets):
|
| - """Prints a given stacktrace.
|
| -
|
| - Args:
|
| - stacktrace_lines: A list of strings which are valid as stacktraces.
|
| - buckets: A dict mapping bucket ids and their corresponding Bucket
|
| - objects.
|
| - """
|
| - for l in stacktrace_lines:
|
| - words = l.split()
|
| - bucket = buckets.get(int(words[BUCKET_ID]))
|
| - if not bucket:
|
| - continue
|
| - for i in range(0, BUCKET_ID - 1):
|
| - sys.stdout.write(words[i] + ' ')
|
| - for address in bucket.stacktrace:
|
| - sys.stdout.write((address_symbol_dict.get(address) or address) + ' ')
|
| - sys.stdout.write('\n')
|
| -
|
| - def dump_stacktrace(self, buckets):
|
| - """Prints stacktraces contained in the log.
|
| -
|
| - Args:
|
| - buckets: A dict mapping bucket ids and their corresponding Bucket
|
| - objects.
|
| - """
|
| - self.dump_stacktrace_lines(self.mmap_stacktrace_lines, buckets)
|
| - self.dump_stacktrace_lines(self.malloc_stacktrace_lines, buckets)
|
| -
|
| - @staticmethod
|
| - def accumulate_size_for_pprof(stacktrace_lines, policy_list, buckets,
|
| - component_name, mmap):
|
| - """Accumulates size of committed chunks and the number of allocated chunks.
|
| -
|
| - Args:
|
| - stacktrace_lines: A list of strings which are valid as stacktraces.
|
| - policy_list: A list containing Policy objects. (Parsed policy data by
|
| - parse_policy.)
|
| - buckets: A dict mapping bucket ids and their corresponding Bucket
|
| - objects.
|
| - component_name: A name of component for filtering.
|
| - mmap: True if searching for a mmap region.
|
| -
|
| - Returns:
|
| - Two integers which are the accumulated size of committed regions and the
|
| - number of allocated chunks, respectively.
|
| - """
|
| - com_committed = 0
|
| - com_allocs = 0
|
| - for l in stacktrace_lines:
|
| - words = l.split()
|
| - bucket = buckets.get(int(words[BUCKET_ID]))
|
| - if (not bucket or
|
| - (component_name and
|
| - component_name != get_component(policy_list, bucket, mmap))):
|
| - continue
|
| -
|
| - com_committed += int(words[COMMITTED])
|
| - com_allocs += int(words[ALLOC_COUNT]) - int(words[FREE_COUNT])
|
| -
|
| - return com_committed, com_allocs
|
| -
|
| - @staticmethod
|
| - def dump_stacktrace_lines_for_pprof(stacktrace_lines, policy_list,
|
| - buckets, component_name, mmap):
|
| - """Prints information of stacktrace lines for pprof.
|
| -
|
| - Args:
|
| - stacktrace_lines: A list of strings which are valid as stacktraces.
|
| - policy_list: A list containing Policy objects. (Parsed policy data by
|
| - parse_policy.)
|
| - buckets: A dict mapping bucket ids and their corresponding Bucket
|
| - objects.
|
| - component_name: A name of component for filtering.
|
| - mmap: True if searching for a mmap region.
|
| - """
|
| - for l in stacktrace_lines:
|
| - words = l.split()
|
| - bucket = buckets.get(int(words[BUCKET_ID]))
|
| - if (not bucket or
|
| - (component_name and
|
| - component_name != get_component(policy_list, bucket, mmap))):
|
| - continue
|
| -
|
| - sys.stdout.write('%6d: %8s [%6d: %8s] @' % (
|
| - int(words[ALLOC_COUNT]) - int(words[FREE_COUNT]),
|
| - words[COMMITTED],
|
| - int(words[ALLOC_COUNT]) - int(words[FREE_COUNT]),
|
| - words[COMMITTED]))
|
| - for address in bucket.stacktrace:
|
| - sys.stdout.write(' ' + address)
|
| - sys.stdout.write('\n')
|
| -
|
| - def dump_for_pprof(self, policy_list, buckets, mapping_lines, component_name):
|
| - """Converts the log file so it can be processed by pprof.
|
| -
|
| - Args:
|
| - policy_list: A list containing Policy objects. (Parsed policy data by
|
| - parse_policy.)
|
| - buckets: A dict mapping bucket ids and their corresponding Bucket
|
| - objects.
|
| - mapping_lines: A list of strings containing /proc/.../maps.
|
| - component_name: A name of component for filtering.
|
| - """
|
| - sys.stdout.write('heap profile: ')
|
| - com_committed, com_allocs = self.accumulate_size_for_pprof(
|
| - self.mmap_stacktrace_lines, policy_list, buckets, component_name,
|
| - True)
|
| - add_committed, add_allocs = self.accumulate_size_for_pprof(
|
| - self.malloc_stacktrace_lines, policy_list, buckets, component_name,
|
| - False)
|
| - com_committed += add_committed
|
| - com_allocs += add_allocs
|
| -
|
| - sys.stdout.write('%6d: %8s [%6d: %8s] @ heapprofile\n' % (
|
| - com_allocs, com_committed, com_allocs, com_committed))
|
| -
|
| - self.dump_stacktrace_lines_for_pprof(
|
| - self.mmap_stacktrace_lines, policy_list, buckets, component_name,
|
| - True)
|
| - self.dump_stacktrace_lines_for_pprof(
|
| - self.malloc_stacktrace_lines, policy_list, buckets, component_name,
|
| - False)
|
| -
|
| - sys.stdout.write('MAPPED_LIBRARIES:\n')
|
| - for l in mapping_lines:
|
| - sys.stdout.write(l)
|
| -
|
| - @staticmethod
|
| - def check_stacktrace_line(stacktrace_line, buckets):
|
| - """Checks if a given stacktrace_line is valid as stacktrace.
|
| -
|
| - Args:
|
| - stacktrace_line: A string to be checked.
|
| - buckets: A dict mapping bucket ids and their corresponding Bucket
|
| - objects.
|
| -
|
| - Returns:
|
| - True if the given stacktrace_line is valid.
|
| - """
|
| - words = stacktrace_line.split()
|
| - if len(words) < BUCKET_ID + 1:
|
| - return False
|
| - if words[BUCKET_ID - 1] != '@':
|
| - return False
|
| - bucket = buckets.get(int(words[BUCKET_ID]))
|
| - if bucket:
|
| - for address in bucket.stacktrace:
|
| - appeared_addresses.add(address)
|
| - return True
|
| -
|
| - @staticmethod
|
| - def skip_lines_while(line_number, max_line_number, skipping_condition):
|
| - """Increments line_number until skipping_condition(line_number) is false.
|
| - """
|
| - while skipping_condition(line_number):
|
| - line_number += 1
|
| - if line_number >= max_line_number:
|
| - sys.stderr.write('invalid heap profile dump.')
|
| - return line_number
|
| - return line_number
|
| -
|
| - def parse_stacktraces_while_valid(self, buckets, log_lines, ln):
|
| - """Parses stacktrace lines while the lines are valid.
|
| -
|
| - Args:
|
| - buckets: A dict mapping bucket ids and their corresponding Bucket
|
| - objects.
|
| - log_lines: A list of lines to be parsed.
|
| - ln: An integer representing the starting line number in log_lines.
|
| -
|
| - Returns:
|
| - A pair of a list of valid lines and an integer representing the last
|
| - line number in log_lines.
|
| - """
|
| - ln = self.skip_lines_while(
|
| - ln, len(log_lines), lambda n: not log_lines[n].split()[0].isdigit())
|
| - stacktrace_lines_start = ln
|
| - ln = self.skip_lines_while(
|
| - ln, len(log_lines),
|
| - lambda n: self.check_stacktrace_line(log_lines[n], buckets))
|
| - return (log_lines[stacktrace_lines_start:ln], ln)
|
| -
|
| - def parse_stacktraces(self, buckets):
|
| - """Parses lines in self.log_lines as stacktrace.
|
| -
|
| - Valid stacktrace lines are stored into self.mmap_stacktrace_lines and
|
| - self.malloc_stacktrace_lines.
|
| -
|
| - Args:
|
| - buckets: A dict mapping bucket ids and their corresponding Bucket
|
| - objects.
|
| -
|
| - Returns:
|
| - A string representing a version of the stacktrace dump. '' for invalid
|
| - dump.
|
| - """
|
| - version = ''
|
| -
|
| - # Skip until an identifiable line.
|
| - headers = ('STACKTRACES:\n', 'MMAP_STACKTRACES:\n', 'heap profile: ')
|
| - ln = self.skip_lines_while(
|
| - 0, len(self.log_lines),
|
| - lambda n: not self.log_lines[n].startswith(headers))
|
| -
|
| - # Identify a version.
|
| - if self.log_lines[ln].startswith('heap profile: '):
|
| - version = self.log_lines[ln][13:].strip()
|
| - if version == DUMP_DEEP_2 or version == DUMP_DEEP_3:
|
| - ln = self.skip_lines_while(
|
| - ln, len(self.log_lines),
|
| - lambda n: self.log_lines[n] != 'MMAP_STACKTRACES:\n')
|
| - else:
|
| - sys.stderr.write(' invalid heap profile dump version:%s\n' % version)
|
| - return ''
|
| - elif self.log_lines[ln] == 'STACKTRACES:\n':
|
| - version = DUMP_DEEP_1
|
| - elif self.log_lines[ln] == 'MMAP_STACKTRACES:\n':
|
| - version = DUMP_DEEP_2
|
| -
|
| - if version == DUMP_DEEP_3:
|
| - sys.stderr.write(' heap profile dump version: %s\n' % version)
|
| - (self.mmap_stacktrace_lines, ln) = self.parse_stacktraces_while_valid(
|
| - buckets, self.log_lines, ln)
|
| - ln = self.skip_lines_while(
|
| - ln, len(self.log_lines),
|
| - lambda n: self.log_lines[n] != 'MALLOC_STACKTRACES:\n')
|
| - (self.malloc_stacktrace_lines, ln) = self.parse_stacktraces_while_valid(
|
| - buckets, self.log_lines, ln)
|
| - return version
|
| -
|
| - elif version == DUMP_DEEP_2:
|
| - sys.stderr.write(' heap profile dump version: %s\n' % version)
|
| - (self.mmap_stacktrace_lines, ln) = self.parse_stacktraces_while_valid(
|
| - buckets, self.log_lines, ln)
|
| - ln = self.skip_lines_while(
|
| - ln, len(self.log_lines),
|
| - lambda n: self.log_lines[n] != 'MALLOC_STACKTRACES:\n')
|
| - (self.malloc_stacktrace_lines, ln) = self.parse_stacktraces_while_valid(
|
| - buckets, self.log_lines, ln)
|
| - self.malloc_stacktrace_lines.extend(self.mmap_stacktrace_lines)
|
| - self.mmap_stacktrace_lines = []
|
| - return version
|
| -
|
| - elif version == DUMP_DEEP_1:
|
| - sys.stderr.write(' heap profile dump version: %s\n' % version)
|
| - (self.malloc_stacktrace_lines, ln) = self.parse_stacktraces_while_valid(
|
| - buckets, self.log_lines, ln)
|
| - return version
|
| -
|
| - else:
|
| - sys.stderr.write(' invalid heap profile dump version:%s\n' % version)
|
| - return ''
|
| -
|
| - def parse_global_stats(self):
|
| - """Parses lines in self.log_lines as global stats."""
|
| - ln = self.skip_lines_while(
|
| - 0, len(self.log_lines),
|
| - lambda n: self.log_lines[n] != 'GLOBAL_STATS:\n')
|
| -
|
| - for prefix in ['total', 'file', 'anonymous', 'other', 'mmap', 'tcmalloc']:
|
| - ln = self.skip_lines_while(
|
| - ln, len(self.log_lines),
|
| - lambda n: self.log_lines[n].split()[0] != prefix)
|
| - words = self.log_lines[ln].split()
|
| - self.counters[prefix + '_virtual'] = int(words[-2])
|
| - self.counters[prefix + '_committed'] = int(words[-1])
|
| -
|
| - def parse_log(self, buckets):
|
| - self.parse_global_stats()
|
| - self.log_version = self.parse_stacktraces(buckets)
|
| -
|
| - @staticmethod
|
| - def accumulate_size_for_policy(stacktrace_lines,
|
| - policy_list, buckets, sizes, mmap):
|
| - for l in stacktrace_lines:
|
| - words = l.split()
|
| - bucket = buckets.get(int(words[BUCKET_ID]))
|
| - component_match = get_component(policy_list, bucket, mmap)
|
| - sizes[component_match] += int(words[COMMITTED])
|
| -
|
| - if component_match.startswith('tc-'):
|
| - sizes['tc-total-log'] += int(words[COMMITTED])
|
| - elif component_match.startswith('mmap-'):
|
| - sizes['mmap-total-log'] += int(words[COMMITTED])
|
| - else:
|
| - sizes['other-total-log'] += int(words[COMMITTED])
|
| -
|
| - def apply_policy(self, policy_list, buckets, first_log_time):
|
| - """Aggregates the total memory size of each component.
|
| -
|
| - Iterate through all stacktraces and attribute them to one of the components
|
| - based on the policy. It is important to apply policy in right order.
|
| -
|
| - Args:
|
| - policy_list: A list containing Policy objects. (Parsed policy data by
|
| - parse_policy.)
|
| - buckets: A dict mapping bucket ids and their corresponding Bucket
|
| - objects.
|
| - first_log_time: An integer representing time when the first log is
|
| - dumped.
|
| -
|
| - Returns:
|
| - A dict mapping components and their corresponding sizes.
|
| - """
|
| -
|
| - sys.stderr.write('apply policy:%s\n' % (self.log_path))
|
| - sizes = dict((c, 0) for c in components)
|
| -
|
| - self.accumulate_size_for_policy(self.mmap_stacktrace_lines,
|
| - policy_list, buckets, sizes, True)
|
| - self.accumulate_size_for_policy(self.malloc_stacktrace_lines,
|
| - policy_list, buckets, sizes, False)
|
| -
|
| - sizes['mmap-no-log'] = self.counters['mmap_committed'] - sizes[
|
| - 'mmap-total-log']
|
| - sizes['mmap-total-record'] = self.counters['mmap_committed']
|
| - sizes['mmap-total-record-vm'] = self.counters['mmap_virtual']
|
| -
|
| - sizes['tc-no-log'] = self.counters['tcmalloc_committed'] - sizes[
|
| - 'tc-total-log']
|
| - sizes['tc-total-record'] = self.counters['tcmalloc_committed']
|
| - sizes['tc-unused'] = sizes['mmap-tcmalloc'] - self.counters[
|
| - 'tcmalloc_committed']
|
| - sizes['tc-total'] = sizes['mmap-tcmalloc']
|
| -
|
| - for key, value in { 'total': 'total_committed',
|
| - 'filemapped': 'file_committed',
|
| - 'anonymous': 'anonymous_committed',
|
| - 'other': 'other_committed',
|
| - 'total-vm': 'total_virtual',
|
| - 'filemapped-vm': 'file_virtual',
|
| - 'anonymous-vm': 'anonymous_virtual',
|
| - 'other-vm': 'other_virtual' }.items():
|
| - if key in sizes:
|
| - sizes[key] = self.counters[value]
|
| -
|
| - if 'unknown' in sizes:
|
| - sizes['unknown'] = self.counters['total_committed'] - self.counters[
|
| - 'mmap_committed']
|
| - if 'total-exclude-profiler' in sizes:
|
| - sizes['total-exclude-profiler'] = self.counters[
|
| - 'total_committed'] - sizes['mmap-profiler']
|
| - if 'hour' in sizes:
|
| - sizes['hour'] = (self.log_time - first_log_time) / 60.0 / 60.0
|
| - if 'minute' in sizes:
|
| - sizes['minute'] = (self.log_time - first_log_time) / 60.0
|
| - if 'second' in sizes:
|
| - sizes['second'] = self.log_time - first_log_time
|
| -
|
| - return sizes
|
| -
|
| - @staticmethod
|
| - def accumulate_size_for_expand(stacktrace_lines, policy_list, buckets,
|
| - component_name, depth, sizes, mmap):
|
| - for line in stacktrace_lines:
|
| - words = line.split()
|
| - bucket = buckets.get(int(words[BUCKET_ID]))
|
| - component_match = get_component(policy_list, bucket, mmap)
|
| - if component_match == component_name:
|
| - stacktrace_sequence = ''
|
| - for address in bucket.stacktrace[1 : min(len(bucket.stacktrace),
|
| - 1 + depth)]:
|
| - stacktrace_sequence += address_symbol_dict[address] + ' '
|
| - if not stacktrace_sequence in sizes:
|
| - sizes[stacktrace_sequence] = 0
|
| - sizes[stacktrace_sequence] += int(words[COMMITTED])
|
| -
|
| - def expand(self, policy_list, buckets, component_name, depth):
|
| - """Prints all stacktraces in a given component of given depth.
|
| -
|
| - Args:
|
| - policy_list: A list containing Policy objects. (Parsed policy data by
|
| - parse_policy.)
|
| - buckets: A dict mapping bucket ids and their corresponding Bucket
|
| - objects.
|
| - component_name: A name of component for filtering.
|
| - depth: An integer representing depth to be printed.
|
| - """
|
| - sizes = {}
|
| -
|
| - self.accumulate_size_for_expand(
|
| - self.mmap_stacktrace_lines, policy_list, buckets, component_name,
|
| - depth, sizes, True)
|
| - self.accumulate_size_for_expand(
|
| - self.malloc_stacktrace_lines, policy_list, buckets, component_name,
|
| - depth, sizes, False)
|
| -
|
| - sorted_sizes_list = sorted(
|
| - sizes.iteritems(), key=(lambda x: x[1]), reverse=True)
|
| - total = 0
|
| - for size_pair in sorted_sizes_list:
|
| - sys.stdout.write('%10d %s\n' % (size_pair[1], size_pair[0]))
|
| - total += size_pair[1]
|
| - sys.stderr.write('total: %d\n' % (total))
|
| -
|
| -
|
| -def update_symbols(symbol_path, mapping_lines, chrome_path):
|
| - """Updates address/symbol mapping on memory and in a .symbol cache file.
|
| -
|
| - It reads cached address/symbol mapping from a .symbol file if it exists.
|
| - Then, it resolves unresolved addresses from a Chrome binary with pprof.
|
| - Both mappings on memory and in a .symbol cache file are updated.
|
| -
|
| - Symbol files are formatted as follows:
|
| - <Address> <Symbol>
|
| - <Address> <Symbol>
|
| - <Address> <Symbol>
|
| - ...
|
| -
|
| - Args:
|
| - symbol_path: A string representing a path for a .symbol file.
|
| - mapping_lines: A list of strings containing /proc/.../maps.
|
| - chrome_path: A string representing a path for a Chrome binary.
|
| - """
|
| - with open(symbol_path, mode='a+') as symbol_f:
|
| - symbol_lines = symbol_f.readlines()
|
| - if symbol_lines:
|
| - for line in symbol_lines:
|
| - items = line.split(None, 1)
|
| - address_symbol_dict[items[0]] = items[1].rstrip()
|
| -
|
| - unresolved_addresses = sorted(
|
| - a for a in appeared_addresses if a not in address_symbol_dict)
|
| -
|
| - if unresolved_addresses:
|
| - with tempfile.NamedTemporaryFile(
|
| - suffix='maps', prefix="dmprof", mode='w+') as pprof_in:
|
| - with tempfile.NamedTemporaryFile(
|
| - suffix='symbols', prefix="dmprof", mode='w+') as pprof_out:
|
| - for line in mapping_lines:
|
| - pprof_in.write(line)
|
| -
|
| - for address in unresolved_addresses:
|
| - pprof_in.write(address + '\n')
|
| -
|
| - pprof_in.seek(0)
|
| -
|
| - p = subprocess.Popen(
|
| - '%s --symbols %s' % (PPROF_PATH, chrome_path),
|
| - shell=True, stdin=pprof_in, stdout=pprof_out)
|
| - p.wait()
|
| -
|
| - pprof_out.seek(0)
|
| - symbols = pprof_out.readlines()
|
| - symbol_f.seek(0, 2)
|
| - for address, symbol in zip(unresolved_addresses, symbols):
|
| - stripped_symbol = symbol.strip()
|
| - address_symbol_dict[address] = stripped_symbol
|
| - symbol_f.write('%s %s\n' % (address, symbol.strip()))
|
| -
|
| -
|
| -def parse_policy(policy_path):
|
| - """Parses policy file.
|
| -
|
| - A policy file contains component's names and their
|
| - stacktrace pattern written in regular expression.
|
| - Those patterns are matched against each symbols of
|
| - each stacktraces in the order written in the policy file
|
| -
|
| - Args:
|
| - policy_path: A path for a policy file.
|
| - Returns:
|
| - A list containing component's name and its regex object
|
| - """
|
| - with open(policy_path, mode='r') as policy_f:
|
| - policy_lines = policy_f.readlines()
|
| -
|
| - policy_version = POLICY_DEEP_1
|
| - if policy_lines[0].startswith('heap profile policy: '):
|
| - policy_version = policy_lines[0][21:].strip()
|
| - policy_lines.pop(0)
|
| - policy_list = []
|
| -
|
| - if policy_version == POLICY_DEEP_2 or policy_version == POLICY_DEEP_1:
|
| - sys.stderr.write(' heap profile policy version: %s\n' % policy_version)
|
| - for line in policy_lines:
|
| - if line[0] == '#':
|
| - continue
|
| -
|
| - if policy_version == POLICY_DEEP_2:
|
| - (name, allocation_type, pattern) = line.strip().split(None, 2)
|
| - mmap = False
|
| - if allocation_type == 'mmap':
|
| - mmap = True
|
| - elif policy_version == POLICY_DEEP_1:
|
| - name = line.split()[0]
|
| - pattern = line[len(name) : len(line)].strip()
|
| - mmap = False
|
| -
|
| - if pattern != 'default':
|
| - policy_list.append(Policy(name, mmap, pattern))
|
| - if components.count(name) == 0:
|
| - components.append(name)
|
| -
|
| - else:
|
| - sys.stderr.write(' invalid heap profile policy version: %s\n' % (
|
| - policy_version))
|
| -
|
| - return policy_list
|
| -
|
| -
|
| -def main():
|
| - if (len(sys.argv) < 4) or (not (sys.argv[1] in ['--csv',
|
| - '--json',
|
| - '--expand',
|
| - '--list',
|
| - '--stacktrace',
|
| - '--pprof'])):
|
| - sys.stderr.write("""Usage:
|
| -%s [options] <chrome-binary> <policy> <profile> [component-name] [depth]
|
| -
|
| -Options:
|
| - --csv Output result in csv format
|
| - --json Output result in json format
|
| - --stacktrace Convert raw address to symbol names
|
| - --list Lists components and their sizes
|
| - --expand Show all stacktraces in the specified component
|
| - of given depth with their sizes
|
| - --pprof Format the profile file so it can be processed
|
| - by pprof
|
| -
|
| -Examples:
|
| - dmprof --csv Debug/chrome dmpolicy hprof.12345.0001.heap > result.csv
|
| - dmprof --json Debug/chrome dmpolicy hprof.12345.0001.heap > result.json
|
| - dmprof --list Debug/chrome dmpolicy hprof.12345.0012.heap
|
| - dmprof --expand Debug/chrome dmpolicy hprof.12345.0012.heap tc-webkit 4
|
| - dmprof --pprof Debug/chrome dmpolicy hprof.12345.0012.heap > for_pprof.txt
|
| -""" % (sys.argv[0]))
|
| - sys.exit(1)
|
| -
|
| - action = sys.argv[1]
|
| - chrome_path = sys.argv[2]
|
| - policy_path = sys.argv[3]
|
| - log_path = sys.argv[4]
|
| -
|
| - sys.stderr.write('parsing a policy file\n')
|
| - policy_list = parse_policy(policy_path)
|
| -
|
| - p = re.compile('\.[0-9][0-9][0-9][0-9]\.heap')
|
| - prefix = p.sub('', log_path)
|
| - symbol_path = prefix + '.symbols'
|
| -
|
| - sys.stderr.write('parsing the maps file\n')
|
| - maps_path = prefix + '.maps'
|
| - with open(maps_path, 'r') as maps_f:
|
| - maps_lines = maps_f.readlines()
|
| -
|
| - # Reading buckets
|
| - sys.stderr.write('parsing the bucket file\n')
|
| - buckets = {}
|
| - bucket_count = 0
|
| - n = 0
|
| - while True:
|
| - buckets_path = '%s.%04d.buckets' % (prefix, n)
|
| - if not os.path.exists(buckets_path):
|
| - if n > 10:
|
| - break
|
| - n += 1
|
| - continue
|
| - sys.stderr.write('reading buckets from %s\n' % (buckets_path))
|
| - with open(buckets_path, 'r') as buckets_f:
|
| - for l in buckets_f:
|
| - words = l.split()
|
| - buckets[int(words[0])] = Bucket(words[1:])
|
| - n += 1
|
| -
|
| - sys.stderr.write('the number buckets: %d\n' % (bucket_count))
|
| -
|
| - log_path_list = [log_path]
|
| -
|
| - if action in ('--csv', '--json'):
|
| - # search for the sequence of files
|
| - n = int(log_path[len(log_path) - 9 : len(log_path) - 5])
|
| - n += 1 # skip current file
|
| - while True:
|
| - p = '%s.%04d.heap' % (prefix, n)
|
| - if os.path.exists(p):
|
| - log_path_list.append(p)
|
| - else:
|
| - break
|
| - n += 1
|
| -
|
| - logs = [Log(path, buckets) for path in log_path_list]
|
| -
|
| - sys.stderr.write('getting symbols\n')
|
| - update_symbols(symbol_path, maps_lines, chrome_path)
|
| -
|
| - # TODO(dmikurube): Many modes now. Split them into separete functions.
|
| - if action == '--stacktrace':
|
| - logs[0].dump_stacktrace(buckets)
|
| -
|
| - elif action == '--csv':
|
| - sys.stdout.write(','.join(components))
|
| - sys.stdout.write('\n')
|
| -
|
| - for log in logs:
|
| - component_sizes = log.apply_policy(policy_list, buckets, logs[0].log_time)
|
| - s = []
|
| - for c in components:
|
| - if c in ('hour', 'minute', 'second'):
|
| - s.append('%05.5f' % (component_sizes[c]))
|
| - else:
|
| - s.append('%05.5f' % (component_sizes[c] / 1024.0 / 1024.0))
|
| - sys.stdout.write(','.join(s))
|
| - sys.stdout.write('\n')
|
| -
|
| - elif action == '--json':
|
| - json_base = {
|
| - 'version': 'JSON_DEEP_1',
|
| - 'legends': components,
|
| - 'snapshots': [],
|
| - }
|
| - for log in logs:
|
| - component_sizes = log.apply_policy(policy_list, buckets, logs[0].log_time)
|
| - component_sizes['log_path'] = log.log_path
|
| - component_sizes['log_time'] = datetime.fromtimestamp(
|
| - log.log_time).strftime('%Y-%m-%d %H:%M:%S')
|
| - json_base['snapshots'].append(component_sizes)
|
| - json.dump(json_base, sys.stdout, indent=2, sort_keys=True)
|
| -
|
| - elif action == '--list':
|
| - component_sizes = logs[0].apply_policy(
|
| - policy_list, buckets, logs[0].log_time)
|
| - for c in components:
|
| - if c in ['hour', 'minute', 'second']:
|
| - sys.stdout.write('%30s %10.3f\n' % (c, component_sizes[c]))
|
| - else:
|
| - sys.stdout.write('%30s %10.3f\n' % (
|
| - c, component_sizes[c] / 1024.0 / 1024.0))
|
| -
|
| - elif action == '--expand':
|
| - component_name = sys.argv[5]
|
| - depth = sys.argv[6]
|
| - logs[0].expand(policy_list, buckets, component_name, int(depth))
|
| -
|
| - elif action == '--pprof':
|
| - if len(sys.argv) > 5:
|
| - logs[0].dump_for_pprof(policy_list, buckets, maps_lines, sys.argv[5])
|
| - else:
|
| - logs[0].dump_for_pprof(policy_list, buckets, maps_lines, None)
|
| -
|
| -
|
| -if __name__ == '__main__':
|
| - sys.exit(main())
|
|
|