blob: 8722243baf611cde0703f550d5b48c9f38138ab9 [file] [log] [blame]
# 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 logging
import os
import re
import struct
LOGGER = logging.getLogger('dmprof')
class PageFrame(object):
"""Represents a pageframe and maybe its shared count."""
def __init__(self, pfn, size, pagecount, start_truncated, end_truncated):
self._pfn = pfn
self._size = size
self._pagecount = pagecount
self._start_truncated = start_truncated
self._end_truncated = end_truncated
def __str__(self):
result = str()
if self._start_truncated:
result += '<'
result += '%06x#%d' % (self._pfn, self._pagecount)
if self._end_truncated:
result += '>'
return result
def __repr__(self):
return str(self)
@staticmethod
def parse(encoded_pfn, size):
start = 0
end = len(encoded_pfn)
end_truncated = False
if encoded_pfn.endswith('>'):
end = len(encoded_pfn) - 1
end_truncated = True
pagecount_found = encoded_pfn.find('#')
pagecount = None
if pagecount_found >= 0:
encoded_pagecount = 'AAA' + encoded_pfn[pagecount_found+1 : end]
pagecount = struct.unpack(
'>I', '\x00' + encoded_pagecount.decode('base64'))[0]
end = pagecount_found
start_truncated = False
if encoded_pfn.startswith('<'):
start = 1
start_truncated = True
pfn = struct.unpack(
'>I', '\x00' + (encoded_pfn[start:end]).decode('base64'))[0]
return PageFrame(pfn, size, pagecount, start_truncated, end_truncated)
@property
def pfn(self):
return self._pfn
@property
def size(self):
return self._size
def set_size(self, size):
self._size = size
@property
def pagecount(self):
return self._pagecount
@property
def start_truncated(self):
return self._start_truncated
@property
def end_truncated(self):
return self._end_truncated
class PFNCounts(object):
"""Represents counts of PFNs in a process."""
_PATH_PATTERN = re.compile(r'^(.*)\.([0-9]+)\.([0-9]+)\.heap$')
def __init__(self, path, modified_time):
matched = self._PATH_PATTERN.match(path)
if matched:
self._pid = int(matched.group(2))
else:
self._pid = 0
self._command_line = ''
self._pagesize = 4096
self._path = path
self._pfn_meta = ''
self._pfnset = {}
self._reason = ''
self._time = modified_time
@staticmethod
def load(path, log_header='Loading PFNs from a heap profile dump: '):
pfnset = PFNCounts(path, float(os.stat(path).st_mtime))
LOGGER.info('%s%s' % (log_header, path))
with open(path, 'r') as pfnset_f:
pfnset.load_file(pfnset_f)
return pfnset
@property
def path(self):
return self._path
@property
def pid(self):
return self._pid
@property
def time(self):
return self._time
@property
def reason(self):
return self._reason
@property
def iter_pfn(self):
for pfn, count in self._pfnset.iteritems():
yield pfn, count
def load_file(self, pfnset_f):
prev_pfn_end_truncated = None
for line in pfnset_f:
line = line.strip()
if line.startswith('GLOBAL_STATS:') or line.startswith('STACKTRACES:'):
break
elif line.startswith('PF: '):
for encoded_pfn in line[3:].split():
page_frame = PageFrame.parse(encoded_pfn, self._pagesize)
if page_frame.start_truncated and (
not prev_pfn_end_truncated or
prev_pfn_end_truncated != page_frame.pfn):
LOGGER.error('Broken page frame number: %s.' % encoded_pfn)
self._pfnset[page_frame.pfn] = self._pfnset.get(page_frame.pfn, 0) + 1
if page_frame.end_truncated:
prev_pfn_end_truncated = page_frame.pfn
else:
prev_pfn_end_truncated = None
elif line.startswith('PageSize: '):
self._pagesize = int(line[10:])
elif line.startswith('PFN: '):
self._pfn_meta = line[5:]
elif line.startswith('PageFrame: '):
self._pfn_meta = line[11:]
elif line.startswith('Time: '):
self._time = float(line[6:])
elif line.startswith('CommandLine: '):
self._command_line = line[13:]
elif line.startswith('Reason: '):
self._reason = line[8:]