# 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:]