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. 4 5 import logging 6 import os 7 import re 8 import struct 9 10 11 LOGGER = logging.getLogger('dmprof') 12 13 14 class PageFrame(object): 15 """Represents a pageframe and maybe its shared count.""" 16 def __init__(self, pfn, size, pagecount, start_truncated, end_truncated): 17 self._pfn = pfn 18 self._size = size 19 self._pagecount = pagecount 20 self._start_truncated = start_truncated 21 self._end_truncated = end_truncated 22 23 def __str__(self): 24 result = str() 25 if self._start_truncated: 26 result += '<' 27 result += '%06x#%d' % (self._pfn, self._pagecount) 28 if self._end_truncated: 29 result += '>' 30 return result 31 32 def __repr__(self): 33 return str(self) 34 35 @staticmethod 36 def parse(encoded_pfn, size): 37 start = 0 38 end = len(encoded_pfn) 39 end_truncated = False 40 if encoded_pfn.endswith('>'): 41 end = len(encoded_pfn) - 1 42 end_truncated = True 43 pagecount_found = encoded_pfn.find('#') 44 pagecount = None 45 if pagecount_found >= 0: 46 encoded_pagecount = 'AAA' + encoded_pfn[pagecount_found+1 : end] 47 pagecount = struct.unpack( 48 '>I', '\x00' + encoded_pagecount.decode('base64'))[0] 49 end = pagecount_found 50 start_truncated = False 51 if encoded_pfn.startswith('<'): 52 start = 1 53 start_truncated = True 54 55 pfn = struct.unpack( 56 '>I', '\x00' + (encoded_pfn[start:end]).decode('base64'))[0] 57 58 return PageFrame(pfn, size, pagecount, start_truncated, end_truncated) 59 60 @property 61 def pfn(self): 62 return self._pfn 63 64 @property 65 def size(self): 66 return self._size 67 68 def set_size(self, size): 69 self._size = size 70 71 @property 72 def pagecount(self): 73 return self._pagecount 74 75 @property 76 def start_truncated(self): 77 return self._start_truncated 78 79 @property 80 def end_truncated(self): 81 return self._end_truncated 82 83 84 class PFNCounts(object): 85 """Represents counts of PFNs in a process.""" 86 87 _PATH_PATTERN = re.compile(r'^(.*)\.([0-9]+)\.([0-9]+)\.heap$') 88 89 def __init__(self, path, modified_time): 90 matched = self._PATH_PATTERN.match(path) 91 if matched: 92 self._pid = int(matched.group(2)) 93 else: 94 self._pid = 0 95 self._command_line = '' 96 self._pagesize = 4096 97 self._path = path 98 self._pfn_meta = '' 99 self._pfnset = {} 100 self._reason = '' 101 self._time = modified_time 102 103 @staticmethod 104 def load(path, log_header='Loading PFNs from a heap profile dump: '): 105 pfnset = PFNCounts(path, float(os.stat(path).st_mtime)) 106 LOGGER.info('%s%s' % (log_header, path)) 107 108 with open(path, 'r') as pfnset_f: 109 pfnset.load_file(pfnset_f) 110 111 return pfnset 112 113 @property 114 def path(self): 115 return self._path 116 117 @property 118 def pid(self): 119 return self._pid 120 121 @property 122 def time(self): 123 return self._time 124 125 @property 126 def reason(self): 127 return self._reason 128 129 @property 130 def iter_pfn(self): 131 for pfn, count in self._pfnset.iteritems(): 132 yield pfn, count 133 134 def load_file(self, pfnset_f): 135 prev_pfn_end_truncated = None 136 for line in pfnset_f: 137 line = line.strip() 138 if line.startswith('GLOBAL_STATS:') or line.startswith('STACKTRACES:'): 139 break 140 elif line.startswith('PF: '): 141 for encoded_pfn in line[3:].split(): 142 page_frame = PageFrame.parse(encoded_pfn, self._pagesize) 143 if page_frame.start_truncated and ( 144 not prev_pfn_end_truncated or 145 prev_pfn_end_truncated != page_frame.pfn): 146 LOGGER.error('Broken page frame number: %s.' % encoded_pfn) 147 self._pfnset[page_frame.pfn] = self._pfnset.get(page_frame.pfn, 0) + 1 148 if page_frame.end_truncated: 149 prev_pfn_end_truncated = page_frame.pfn 150 else: 151 prev_pfn_end_truncated = None 152 elif line.startswith('PageSize: '): 153 self._pagesize = int(line[10:]) 154 elif line.startswith('PFN: '): 155 self._pfn_meta = line[5:] 156 elif line.startswith('PageFrame: '): 157 self._pfn_meta = line[11:] 158 elif line.startswith('Time: '): 159 self._time = float(line[6:]) 160 elif line.startswith('CommandLine: '): 161 self._command_line = line[13:] 162 elif line.startswith('Reason: '): 163 self._reason = line[8:] 164