Home | History | Annotate | Download | only in lib
      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