[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