[gs-cvs] rev 8420 - trunk/gs/toolbin
lpd at ghostscript.com
lpd at ghostscript.com
Fri Nov 30 14:13:49 PST 2007
Author: lpd
Date: 2007-11-30 14:13:49 -0800 (Fri, 30 Nov 2007)
New Revision: 8420
Added:
trunk/gs/toolbin/memory.py
Log:
Add a tool that analyzes logs produced by gs -Z67, producing a report of
memory leaks.
Added: trunk/gs/toolbin/memory.py
===================================================================
--- trunk/gs/toolbin/memory.py 2007-11-30 06:43:47 UTC (rev 8419)
+++ trunk/gs/toolbin/memory.py 2007-11-30 22:13:49 UTC (rev 8420)
@@ -0,0 +1,238 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2007 Aladdin Enterprises. All rights reserved.
+
+# This script analyzes the output of Ghostscript run with -Z67.
+# Its primary purpose is detecting memory leaks.
+
+# $Id: $
+
+USAGE = """\
+Usage: python memory.py z67trace > report
+ where z67trace is the output of gs -Z67"""
+HELP = """\
+An example of usage:
+ gs -Z67 somefile.ps >& somefile.log
+ python memory.py somefile.log > somefile.report
+"""
+
+__author__ = 'L Peter Deutsch'
+
+import re
+from cStringIO import StringIO
+from difflib import SequenceMatcher
+
+#---------------- Memory representation ----------------#
+
+class struct(object):
+ # Instance variables:
+ # address (int) - the address of the object
+ pass
+
+class Store(object):
+
+ def __init__(self):
+ self.memories = []
+
+ def totals(self):
+ o = s = a = n = 0
+ for memory in self.memories:
+ for chunk in memory.chunks:
+ o += chunk.otop - chunk.obot
+ s += chunk.stop - chunk.sbot
+ for obj in chunk.objects:
+ if not obj.isfree:
+ n += 1
+ a += obj.size
+ return '%d object space (%d objects, %d total size), %d strings' % \
+ (o, n, a, s)
+
+ def compare(self, store):
+ ml, sml = [[(m.address, m.space, m.level) \
+ for m in s.memories]
+ for s in [self, store]]
+ if ml != sml:
+ return 'Memory lists differ'
+ buf = StringIO()
+ for m, sm in zip(self.memories, store.memories):
+ buf.write('Memory 0x%x, space = %d, level = %d:\n' % \
+ (m.address, m.space, m.level))
+ buf.write(m.compare(sm))
+ return buf.getvalue()
+
+class Memory(struct):
+
+ def __init__(self, store, address, space, level):
+ self.address, self.space, self.level = address, space, level
+ self.chunks = []
+ if store: store.memories.append(self)
+
+ def compare(self, memory):
+ buf = StringIO()
+ cdict = dict([(c.address, c) for c in self.chunks])
+ mcdict = dict([(c.address, c) for c in memory.chunks])
+ for a in cdict.keys():
+ if a not in mcdict:
+ buf.write('Freed: ')
+ buf.write(cdict[a].listing())
+ for a in mcdict.keys():
+ if a not in cdict:
+ buf.write('Added: ')
+ buf.write(mcdict[a].listing())
+ for a, c in cdict.items():
+ if a in mcdict:
+ buf.write(c.compare(mcdict[a]))
+ return buf.getvalue()
+
+class Chunk(struct):
+
+ # obot, otop, sbot, stop correspond to chunk_t.cbase, cbot, ctop, climit.
+ def __init__(self, memory, address, obot, otop, sbot, stop, cend):
+ self.address = address
+ self.obot, self.otop = obot, otop
+ self.sbot, self.stop = sbot, stop
+ self.cend = cend
+ self.objects = []
+ if memory: memory.chunks.append(self)
+
+ def compare(self, chunk):
+ buf = StringIO()
+ o, s = self.otop - self.obot, self.stop - self.sbot
+ co, cs = chunk.otop - chunk.obot, chunk.stop - chunk.sbot
+ if co != o or cs != s:
+ buf.write('objects %+d, strings %+d' % (co - o, cs - s))
+ buf.write('\n')
+ # Use difflib to find the differences between the two chunks.
+ seq1 = [b.content for b in self.objects if not b.isfree]
+ seq2 = [b.content for b in chunk.objects if not b.isfree]
+ m = SequenceMatcher(None, seq1, seq2)
+ pi = pj = 0
+ for i, j, n in m.get_matching_blocks():
+ while pi < i:
+ buf.write('- %s\n' % self.objects[pi])
+ pi += 1
+ while pj < j:
+ buf.write('+ %s\n' % chunk.objects[pj])
+ pj += 1
+ pi, pj = pi + n, pj + n
+ if buf.tell() > 1:
+ return 'Chunk 0x%x: ' % self.address + buf.getvalue()
+ else:
+ return ''
+
+ def listing(self):
+ buf = StringIO()
+ buf.write('chunk at 0x%x: %d used, %d free \n' % \
+ (self.address,
+ sum([o.size for o in self.objects if not o.isfree]),
+ sum([o.size for o in self.objects if o.isfree])))
+ for obj in self.objects:
+ buf.write(' %s\n' % obj)
+ return buf.getvalue()
+
+class block(struct):
+
+ content = property(lambda b: (b.name, b.size))
+
+ def __init__(self, chunk, address, size):
+ self.address, self.size = address, size
+ if chunk: chunk.objects.append(self)
+
+ def __str__(self):
+ return '0x%x: %s (%d)' % (self.address, self.name, self.size)
+
+class Object(block):
+ isfree = False
+ def __init__(self, chunk, name, address, size):
+ self.name = name
+ block.__init__(self, chunk, address, size)
+
+class Free(block):
+ isfree = True
+ name = '(free)'
+
+#---------------- Log reader ----------------#
+
+# Parse the log entries produced by -Z67.
+res_hex = '0x([0-9a-f]+)'
+res_dec = '([-0-9]+)'
+re_memory = re.compile(r'validating memory %s, space %s, [^0-9]*%s' % \
+ (res_hex, res_dec, res_dec))
+re_chunk = re.compile(r'validating chunk %s \(%s\.\.%s, %s\.\.%s\.\.%s\)$' % \
+ (6 * (res_hex,)))
+re_object = re.compile(r'validating ([^(]+)\(%s\) %s$' % \
+ (res_dec, res_hex))
+re_free = re.compile(r'validating \(free\)\(%s\) %s$' % \
+ (res_dec, res_hex))
+
+class Log:
+
+ def __init__(self):
+ self.stores = []
+
+ def readlog(self, fname):
+ # Read a log produced by -Z67. Each separate validation trace is a
+ # separate instance of Store. Note that each GC produces two
+ # Stores, one from pre-validation, one from post-validation.
+ f, store = file(fname), None
+ memory = chunk = None
+ for line in f:
+ line = line.strip()
+ if line.startswith('[6]validating memory '):
+ addr, space, level = re_memory.match(line[3:]).groups()
+ if not store: store = Store()
+ memory = Memory(store, int(addr, 16), int(space), int(level))
+ chunk = None
+ elif line.startswith('[6]validating chunk '):
+ cvalues = re_chunk.match(line[3:]).groups()
+ chunk = Chunk(memory, *[int(v, 16) for v in cvalues])
+ elif line.startswith('[7]validating (free)'):
+ size, addr = re_free.match(line[3:]).groups()
+ Free(chunk, int(addr, 16), int(size))
+ elif line.startswith('[7]validating '):
+ name, size, addr = re_object.match(line[3:]).groups()
+ Object(chunk, name, int(addr, 16), int(size))
+ elif line[2:].startswith(']validating'):
+ print '**** unknown:', line
+ elif _is_end_trace(line):
+ self.stores.append(store)
+ store = None
+ f.close()
+
+ def compare(self, which = slice(3, -2, 2)):
+ buf = StringIO()
+ stores = self.stores
+ indices = range(*which.indices(len(stores)))
+ for i1, i2 in zip(indices[:-1], indices[1:]):
+ buf.write('Comparing %d and %d\n' % (i1, i2))
+ for j in [i1, i2]:
+ buf.write('%3d: %s\n' % (j, stores[j].totals()))
+ buf.write(stores[i1].compare(stores[i2]))
+ buf.write(64 * '-' + '\n')
+ for j in [indices[0], indices[-1]]:
+ buf.write('%3d: %s\n' % (j, stores[j].totals()))
+ return buf.getvalue()
+
+def _is_end_trace(line):
+ return line.startswith('[6]---------------- end ') and \
+ line.endswith('validate pointers ----------------')
+
+#---------------- Main program ----------------#
+
+def main(argv):
+ args = argv[1:]
+ if len(args) != 1:
+ print 'Use --help for usage information.'
+ return
+ if args[0] == '--help':
+ print USAGE
+ print HELP
+ return
+ log = Log()
+ log.readlog(args[0])
+ print len(log.stores), 'stores'
+ print log.compare()
+
+if __name__ == '__main__':
+ import sys
+ sys.exit(main(sys.argv) or 0)
Property changes on: trunk/gs/toolbin/memory.py
___________________________________________________________________
Name: svn:executable
+ *
More information about the gs-cvs
mailing list