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
5import logging
6import os
7import re
8import struct
9
10
11LOGGER = logging.getLogger('dmprof')
12
13
14class 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
84class 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