[Checkins] SVN: z3c.memhunt.objgraph/trunk/ Importing rough z3c.memhunt.objgraph
Daniel Blackburn
blackburnd at gmail.com
Wed Nov 10 05:26:41 EST 2010
Log message for revision 118310:
Importing rough z3c.memhunt.objgraph
Changed:
A z3c.memhunt.objgraph/trunk/
A z3c.memhunt.objgraph/trunk/CHANGES.txt
A z3c.memhunt.objgraph/trunk/README.txt
A z3c.memhunt.objgraph/trunk/docs/
A z3c.memhunt.objgraph/trunk/docs/HISTORY.txt
A z3c.memhunt.objgraph/trunk/resurrect.c
A z3c.memhunt.objgraph/trunk/setup.cfg
A z3c.memhunt.objgraph/trunk/setup.py
A z3c.memhunt.objgraph/trunk/z3c/
A z3c.memhunt.objgraph/trunk/z3c/__init__.py
A z3c.memhunt.objgraph/trunk/z3c/memhunt/
A z3c.memhunt.objgraph/trunk/z3c/memhunt/__init__.py
A z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/
A z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/README.txt
A z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/__init__.py
A z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/checks.py
A z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/cond_perms.py
A z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/configure.zcml
A z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/configure_zope2.zcml
A z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/configure_zope3.zcml
A z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/debug_views.py
A z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/objgraph-1.py
A z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/objgraph.py
A z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/objgraph_orig.py
A z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/objgraph_trunk.diff
A z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/resources/
A z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/resources/history/
A z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/resources/history/keep.txt
A z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/resources/keep.txt
A z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/version.txt
A z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/zpt/
A z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/zpt/debug.css
A z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/zpt/ref_common_count.pt
A z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/zpt/ref_count.pt
A z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/zpt/start.pt
A z3c.memhunt.objgraph/trunk/z3c.memhunt.objgraph.egg-info/
A z3c.memhunt.objgraph/trunk/z3c.memhunt.objgraph.egg-info/PKG-INFO
A z3c.memhunt.objgraph/trunk/z3c.memhunt.objgraph.egg-info/SOURCES.txt
A z3c.memhunt.objgraph/trunk/z3c.memhunt.objgraph.egg-info/dependency_links.txt
A z3c.memhunt.objgraph/trunk/z3c.memhunt.objgraph.egg-info/namespace_packages.txt
A z3c.memhunt.objgraph/trunk/z3c.memhunt.objgraph.egg-info/not-zip-safe
A z3c.memhunt.objgraph/trunk/z3c.memhunt.objgraph.egg-info/requires.txt
A z3c.memhunt.objgraph/trunk/z3c.memhunt.objgraph.egg-info/top_level.txt
-=-
Added: z3c.memhunt.objgraph/trunk/CHANGES.txt
===================================================================
--- z3c.memhunt.objgraph/trunk/CHANGES.txt (rev 0)
+++ z3c.memhunt.objgraph/trunk/CHANGES.txt 2010-11-10 10:26:41 UTC (rev 118310)
@@ -0,0 +1,4 @@
+
+0.0.1
+-----
+- Initial eggification of hc.debug.core
Added: z3c.memhunt.objgraph/trunk/README.txt
===================================================================
--- z3c.memhunt.objgraph/trunk/README.txt (rev 0)
+++ z3c.memhunt.objgraph/trunk/README.txt 2010-11-10 10:26:41 UTC (rev 118310)
@@ -0,0 +1,4 @@
+This is a global debug package used to hold debug code across any
+site.
+
+This package uses objgraph for introspection views
\ No newline at end of file
Added: z3c.memhunt.objgraph/trunk/docs/HISTORY.txt
===================================================================
--- z3c.memhunt.objgraph/trunk/docs/HISTORY.txt (rev 0)
+++ z3c.memhunt.objgraph/trunk/docs/HISTORY.txt 2010-11-10 10:26:41 UTC (rev 118310)
@@ -0,0 +1,2 @@
+Changes
+=======
Added: z3c.memhunt.objgraph/trunk/resurrect.c
===================================================================
--- z3c.memhunt.objgraph/trunk/resurrect.c (rev 0)
+++ z3c.memhunt.objgraph/trunk/resurrect.c 2010-11-10 10:26:41 UTC (rev 118310)
@@ -0,0 +1,30 @@
+#include "Python.h"
+
+static PyObject *
+Py_conjure (PyObject * self, PyObject * args)
+{
+ long address;
+ if (!PyArg_ParseTuple (args, "l", &address)) {
+ return NULL;
+ } else {
+ PyObject * thing = (PyObject *) address;
+ Py_INCREF(thing);
+ return thing;
+ }
+}
+
+/* List of functions defined in the module */
+
+static PyMethodDef resurrect_methods[] = {
+ {"conjure",Py_conjure,1},
+ {NULL,NULL}/* sentinel */
+};
+
+/* Initialization function for the module (*must* be called initresurrect) */
+
+void
+initresurrect()
+{
+ PyObject *m;
+ m = Py_InitModule("resurrect", resurrect_methods);
+}
Property changes on: z3c.memhunt.objgraph/trunk/resurrect.c
___________________________________________________________________
Added: svn:executable
+
Added: z3c.memhunt.objgraph/trunk/setup.cfg
===================================================================
--- z3c.memhunt.objgraph/trunk/setup.cfg (rev 0)
+++ z3c.memhunt.objgraph/trunk/setup.cfg 2010-11-10 10:26:41 UTC (rev 118310)
@@ -0,0 +1,3 @@
+[egg_info]
+tag_build = dev
+tag_svn_revision = true
Added: z3c.memhunt.objgraph/trunk/setup.py
===================================================================
--- z3c.memhunt.objgraph/trunk/setup.py (rev 0)
+++ z3c.memhunt.objgraph/trunk/setup.py 2010-11-10 10:26:41 UTC (rev 118310)
@@ -0,0 +1,66 @@
+# -*- coding: utf-8 -*-
+"""
+This module contains the tool of z3c.memhunt.objgraph
+
+"""
+import os
+from setuptools import setup, find_packages
+from distutils.core import setup, Extension
+
+def read(*rnames):
+ return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
+
+libname = 'z3c.memhunt.objgraph'
+libloc = libname.split('.')
+version = open(os.path.join(*libloc+['version.txt'])).read().strip()
+
+
+long_description = (
+ read('CHANGES.txt')
+ + '\n' +
+ read('README.txt')
+ + '\n' +
+ 'Change history\n'
+ '**************\n'
+ )
+
+tests_require=['zope.testing']
+
+setup(name='z3c.memhunt.objgraph',
+ version=version,
+ description="",
+ long_description=long_description,
+ # Get more strings from http://www.python.org/pypi?%3Aaction=list_classifiers
+ keywords = "zope zope2 zope3 memory objgraph graphviz guppy",
+ classifiers = [
+ 'Development Status :: 4 - Beta',
+ 'Environment :: Web Environment',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: Zope Public License',
+ 'Programming Language :: Python',
+ 'Natural Language :: English',
+ 'Operating System :: OS Independent',
+ 'Topic :: Internet :: WWW/HTTP',
+ 'Framework :: Zope3'],
+ author='Daniel Blackburn & Holmes Corporation',
+ author_email='danielb at holmescorp.com',
+ url="''",
+ license = "ZPL 2.1",
+ packages=find_packages(exclude=['ez_setup']),
+ namespace_packages=['z3c',],
+ include_package_data=True,
+ zip_safe=False,
+ install_requires=['setuptools',
+ 'guppy',
+ 'objgraph',
+ ],
+ tests_require=tests_require,
+# ext_modules = [Extension("resurrect", ["resurrect.c"]),
+# Extension("malloc_stats", ["malloc_stats.c"])],
+ extras_require=dict(tests=tests_require),
+ test_suite = 'z3c.memhunt.objgraph.tests.test_docs.test_suite',
+ )
+
+
+
+
Added: z3c.memhunt.objgraph/trunk/z3c/__init__.py
===================================================================
--- z3c.memhunt.objgraph/trunk/z3c/__init__.py (rev 0)
+++ z3c.memhunt.objgraph/trunk/z3c/__init__.py 2010-11-10 10:26:41 UTC (rev 118310)
@@ -0,0 +1,6 @@
+# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
+try:
+ __import__('pkg_resources').declare_namespace(__name__)
+except ImportError:
+ from pkgutil import extend_path
+ __path__ = extend_path(__path__, __name__)
Property changes on: z3c.memhunt.objgraph/trunk/z3c/__init__.py
___________________________________________________________________
Added: svn:executable
+
Added: z3c.memhunt.objgraph/trunk/z3c/memhunt/__init__.py
===================================================================
--- z3c.memhunt.objgraph/trunk/z3c/memhunt/__init__.py (rev 0)
+++ z3c.memhunt.objgraph/trunk/z3c/memhunt/__init__.py 2010-11-10 10:26:41 UTC (rev 118310)
@@ -0,0 +1,6 @@
+# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
+try:
+ __import__('pkg_resources').declare_namespace(__name__)
+except ImportError:
+ from pkgutil import extend_path
+ __path__ = extend_path(__path__, __name__)
Property changes on: z3c.memhunt.objgraph/trunk/z3c/memhunt/__init__.py
___________________________________________________________________
Added: svn:executable
+
Added: z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/README.txt
===================================================================
--- z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/README.txt (rev 0)
+++ z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/README.txt 2010-11-10 10:26:41 UTC (rev 118310)
@@ -0,0 +1,5 @@
+
+Testing Debug views
+===================
+ >>> browser.open('http://nohost/plone/accessmanager/ref_count')
+
Added: z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/__init__.py
===================================================================
--- z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/__init__.py (rev 0)
+++ z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/__init__.py 2010-11-10 10:26:41 UTC (rev 118310)
@@ -0,0 +1 @@
+#
Added: z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/checks.py
===================================================================
--- z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/checks.py (rev 0)
+++ z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/checks.py 2010-11-10 10:26:41 UTC (rev 118310)
@@ -0,0 +1,503 @@
+"""
+Ad-hoc tools for drawing Python object reference graphs with graphviz.
+
+This module is more useful as a repository of sample code and ideas, than
+as a finished product. For documentation and background, read
+
+ http://mg.pov.lt/blog/hunting-python-memleaks.html
+ http://mg.pov.lt/blog/python-object-graphs.html
+ http://mg.pov.lt/blog/object-graphs-with-graphviz.html
+
+in that order. Then use pydoc to read the docstrings, as there were
+improvements made since those blog posts.
+
+Copyright (c) 2008-2010 Marius Gedminas <marius at pov.lt>
+
+Released under the MIT licence.
+
+
+Changes
+=======
+
+1.3.2 (unreleased)
+------------------
+
+Compatibility with Python 2.4 and 2.5 (tempfile.NamedTemporaryFile has no
+delete argument).
+
+New function: most_common_types().
+
+
+1.3.1 (2010-07-17)
+------------------
+
+Rebuild an sdist with no missing files (fixes LP#606604).
+
+Added MANIFEST.in and a Makefile to check that setup.py sdist generates
+source distributions with no files missing.
+
+
+1.3 (2010-07-13)
+----------------
+
+Highlight objects with a __del__ method.
+
+Fixes LP#483411: suggest always passing [obj] to show_refs, show_backrefs,
+since obj might be a list/tuple.
+
+Fixes LP#514422: show_refs, show_backrefs don't create files in the current
+working directory any more. Instead they accept a filename argument, which
+can be a .dot file or a .png file. If None or not specified, those functions
+will try to spawn xdot as before.
+
+New extra_info argument to graph-generating functions (patch by Thouis Jones,
+LP#558914).
+
+setup.py should work with distutils now (LP#604430, thanks to Randy Heydon).
+
+
+1.2 (2009-03-25)
+----------------
+
+Project website, public source repository, uploaded to PyPI.
+
+No code changes.
+
+
+1.1dev (2008-09-05)
+-------------------
+
+New function: show_refs() for showing forward references.
+
+New functions: typestats() and show_most_common_types().
+
+Object boxes are less crammed with useless information (such as IDs).
+
+Spawns xdot if it is available.
+"""
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+__author__ = "Marius Gedminas (marius at gedmin.as)"
+__copyright__ = "Copyright (c) 2008-2010 Marius Gedminas"
+__license__ = "MIT"
+__version__ = "1.3.2dev"
+__date__ = "2010-11-02"
+
+
+import gc
+import inspect
+import types
+import weakref
+import operator
+import os
+import subprocess
+import tempfile
+
+
+def count(typename):
+ """Count objects tracked by the garbage collector with a given class name.
+
+ Example:
+
+ >>> count('dict')
+ 42
+ >>> count('MyClass')
+ 3
+
+ Note that the GC does not track simple objects like int or str.
+ """
+ return sum(1 for o in gc.get_objects() if type(o).__name__ == typename)
+
+
+def typestats():
+ """Count the number of instances for each type tracked by the GC.
+
+ Note that the GC does not track simple objects like int or str.
+
+ Note that classes with the same name but defined in different modules
+ will be lumped together.
+ """
+ stats = {}
+ for o in gc.get_objects():
+ stats.setdefault(type(o).__name__, 0)
+ stats[type(o).__name__] += 1
+ return stats
+
+
+def most_common_types(limit=10):
+ """Count the names of types with the most instances.
+
+ Note that the GC does not track simple objects like int or str.
+
+ Note that classes with the same name but defined in different modules
+ will be lumped together.
+
+ Returns a list of (type_name, count), sorted most-frequent-first.
+ """
+ stats = sorted(typestats().items(), key=operator.itemgetter(1),
+ reverse=True)
+ if limit:
+ stats = stats[:limit]
+ return stats
+
+
+def show_most_common_types(limit=10):
+ """Print the table of types of most common instances
+
+ Note that the GC does not track simple objects like int or str.
+
+ Note that classes with the same name but defined in different modules
+ will be lumped together.
+ """
+ stats = most_common_types(limit)
+ width = max(len(name) for name, count in stats)
+ for name, count in stats:
+ print name.ljust(width), count
+
+
+def by_type(typename):
+ """Return objects tracked by the garbage collector with a given class name.
+
+ Example:
+
+ >>> by_type('MyClass')
+ [<mymodule.MyClass object at 0x...>]
+
+ Note that the GC does not track simple objects like int or str.
+ """
+ return [o for o in gc.get_objects() if type(o).__name__ == typename]
+
+
+def at(addr):
+ """Return an object at a given memory address.
+
+ The reverse of id(obj):
+
+ >>> at(id(obj)) is obj
+ True
+
+ Note that this function does not work on objects that are not tracked by
+ the GC (e.g. ints or strings).
+ """
+ for o in gc.get_objects():
+ if id(o) == addr:
+ return o
+ return None
+
+
+def find_backref_chain(obj, predicate, max_depth=20, extra_ignore=()):
+ """Find a shortest chain of references leading to obj.
+
+ The start of the chain will be some object that matches your predicate.
+
+ ``max_depth`` limits the search depth.
+
+ ``extra_ignore`` can be a list of object IDs to exclude those objects from
+ your search.
+
+ Example:
+
+ >>> find_backref_chain(obj, inspect.ismodule)
+ [<module ...>, ..., obj]
+
+ Returns None if such a chain could not be found.
+ """
+ queue = [obj]
+ depth = {id(obj): 0}
+ parent = {id(obj): None}
+ ignore = set(extra_ignore)
+ ignore.add(id(extra_ignore))
+ ignore.add(id(queue))
+ ignore.add(id(depth))
+ ignore.add(id(parent))
+ ignore.add(id(ignore))
+ gc.collect()
+ while queue:
+ target = queue.pop(0)
+ if predicate(target):
+ chain = [target]
+ while parent[id(target)] is not None:
+ target = parent[id(target)]
+ chain.append(target)
+ return chain
+ tdepth = depth[id(target)]
+ if tdepth < max_depth:
+ referrers = gc.get_referrers(target)
+ ignore.add(id(referrers))
+ for source in referrers:
+ if inspect.isframe(source) or id(source) in ignore:
+ continue
+ if id(source) not in depth:
+ depth[id(source)] = tdepth + 1
+ parent[id(source)] = target
+ queue.append(source)
+ return None # not found
+
+
+def show_backrefs(objs, max_depth=3, extra_ignore=(), filter=None, too_many=10,
+ highlight=None, filename=None, extra_info=(lambda _: '')):
+ """Generate an object reference graph ending at ``objs``
+
+ The graph will show you what objects refer to ``objs``, directly and
+ indirectly.
+
+ ``objs`` can be a single object, or it can be a list of objects. If
+ unsure, wrap the single object in a new list.
+
+ Produces a Graphviz .dot file and spawns a viewer (xdot) if one is
+ installed, otherwise converts the graph to a .png image.
+
+ Use ``max_depth`` and ``too_many`` to limit the depth and breadth of the
+ graph.
+
+ Use ``filter`` (a predicate) and ``extra_ignore`` (a list of object IDs) to
+ remove undesired objects from the graph.
+
+ Use ``highlight`` (a predicate) to highlight certain graph nodes in blue.
+
+ Use ``extra_info`` (a function returning a string) to report extra
+ information for objects.
+
+ Examples:
+
+ >>> show_backrefs(obj)
+ >>> show_backrefs([obj1, obj2])
+ >>> show_backrefs(obj, max_depth=5)
+ >>> show_backrefs(obj, filter=lambda x: not inspect.isclass(x))
+ >>> show_backrefs(obj, highlight=inspect.isclass)
+ >>> show_backrefs(obj, extra_ignore=[id(locals())])
+
+ """
+ show_graph(objs, max_depth=max_depth, extra_ignore=extra_ignore,
+ filter=filter, too_many=too_many, highlight=highlight,
+ edge_func=gc.get_referrers, swap_source_target=False,
+ filename=filename, extra_info=extra_info)
+
+
+def show_refs(objs, max_depth=3, extra_ignore=(), filter=None, too_many=10,
+ highlight=None, filename=None, extra_info=(lambda _: '')):
+ """Generate an object reference graph starting at ``objs``
+
+ The graph will show you what objects are reachable from ``objs``, directly
+ and indirectly.
+
+ ``objs`` can be a single object, or it can be a list of objects. If
+ unsure, wrap the single object in a new list.
+
+ Produces a Graphviz .dot file and spawns a viewer (xdot) if one is
+ installed, otherwise converts the graph to a .png image.
+
+ Use ``max_depth`` and ``too_many`` to limit the depth and breadth of the
+ graph.
+
+ Use ``filter`` (a predicate) and ``extra_ignore`` (a list of object IDs) to
+ remove undesired objects from the graph.
+
+ Use ``highlight`` (a predicate) to highlight certain graph nodes in blue.
+
+ Use ``extra_info`` (a function returning a string) to report extra
+ information for objects.
+
+ Examples:
+
+ >>> show_refs(obj)
+ >>> show_refs([obj1, obj2])
+ >>> show_refs(obj, max_depth=5)
+ >>> show_refs(obj, filter=lambda x: not inspect.isclass(x))
+ >>> show_refs(obj, highlight=inspect.isclass)
+ >>> show_refs(obj, extra_ignore=[id(locals())])
+
+ """
+ show_graph(objs, max_depth=max_depth, extra_ignore=extra_ignore,
+ filter=filter, too_many=too_many, highlight=highlight,
+ edge_func=gc.get_referents, swap_source_target=True,
+ filename=filename, extra_info=extra_info)
+
+#
+# Internal helpers
+#
+
+def show_graph(objs, edge_func, swap_source_target,
+ max_depth=3, extra_ignore=(), filter=None, too_many=10,
+ highlight=None, filename=None, extra_info=(lambda _: '')):
+ if not isinstance(objs, (list, tuple)):
+ objs = [objs]
+ if filename and filename.endswith('.dot'):
+ f = file(filename, 'w')
+ dot_filename = filename
+ else:
+ fd, dot_filename = tempfile.mkstemp('.dot', text=True)
+ f = os.fdopen(fd, "w")
+ print >> f, 'digraph ObjectGraph {'
+ print >> f, ' node[shape=box, style=filled, fillcolor=white];'
+ queue = []
+ depth = {}
+ ignore = set(extra_ignore)
+ ignore.add(id(objs))
+ ignore.add(id(extra_ignore))
+ ignore.add(id(queue))
+ ignore.add(id(depth))
+ ignore.add(id(ignore))
+ for obj in objs:
+ print >> f, ' %s[fontcolor=red];' % (obj_node_id(obj))
+ depth[id(obj)] = 0
+ queue.append(obj)
+ gc.collect()
+ nodes = 0
+ while queue:
+ nodes += 1
+ target = queue.pop(0)
+ tdepth = depth[id(target)]
+ print >> f, ' %s[label="%s"];' % (obj_node_id(target), obj_label(target, tdepth, extra_info))
+ h, s, v = gradient((0, 0, 1), (0, 0, .3), tdepth, max_depth)
+ if inspect.ismodule(target):
+ h = .3
+ s = 1
+ if highlight and highlight(target):
+ h = .6
+ s = .6
+ v = 0.5 + v * 0.5
+ print >> f, ' %s[fillcolor="%g,%g,%g"];' % (obj_node_id(target), h, s, v)
+ if v < 0.5:
+ print >> f, ' %s[fontcolor=white];' % (obj_node_id(target))
+ if hasattr(target, '__del__'):
+ print >> f, " %s->%s_has_a_del[color=red,style=dotted,len=0.25,weight=10];" % (obj_node_id(target), obj_node_id(target))
+ print >> f, ' %s_has_a_del[label="__del__",shape=doublecircle,height=0.25,color=red,fillcolor="0,.5,1",fontsize=6];' % (obj_node_id(target))
+ if inspect.ismodule(target) or tdepth >= max_depth:
+ continue
+ neighbours = edge_func(target)
+ ignore.add(id(neighbours))
+ n = 0
+ for source in neighbours:
+ if inspect.isframe(source) or id(source) in ignore:
+ continue
+ if filter and not filter(source):
+ continue
+ if swap_source_target:
+ srcnode, tgtnode = target, source
+ else:
+ srcnode, tgtnode = source, target
+ elabel = edge_label(srcnode, tgtnode)
+ print >> f, ' %s -> %s%s;' % (obj_node_id(srcnode), obj_node_id(tgtnode), elabel)
+ if id(source) not in depth:
+ depth[id(source)] = tdepth + 1
+ queue.append(source)
+ n += 1
+ if n >= too_many:
+ print >> f, ' %s[color=red];' % obj_node_id(target)
+ break
+ print >> f, "}"
+ f.close()
+ print "Graph written to %s (%d nodes)" % (dot_filename, nodes)
+ if filename is None and program_in_path('xdot'):
+ print "Spawning graph viewer (xdot)"
+ subprocess.Popen(['xdot', dot_filename])
+ elif program_in_path('dot'):
+ if filename is None:
+ print "Graph viewer (xdot) not found, generating a png instead"
+ if filename and filename.endswith('.png'):
+ f = file(filename, 'wb')
+ png_filename = filename
+ else:
+ if filename is not None:
+ print "Unrecognized file type (%s)" % filename
+ fd, png_filename = tempfile.mkstemp('.png', text=False)
+ f = os.fdopen(fd, "wb")
+ dot = subprocess.Popen(['dot', '-Tpng', dot_filename],
+ stdout=f)
+ dot.wait()
+ f.close()
+ print "Image generated as %s" % png_filename
+ else:
+ if filename is None:
+ print "Graph viewer (xdot) and image renderer (dot) not found, not doing anything else"
+ else:
+ print "Unrecognized file type (%s), not doing anything else" % filename
+
+
+def obj_node_id(obj):
+ if isinstance(obj, weakref.ref):
+ return 'all_weakrefs_are_one'
+ return ('o%d' % id(obj)).replace('-', '_')
+
+
+def obj_label(obj, depth, extra_info):
+ return quote(type(obj).__name__ + ':\n' +
+ safe_repr(obj) + '\n' +
+ extra_info(obj))
+
+
+def quote(s):
+ return s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n")
+
+
+def safe_repr(obj):
+ try:
+ return short_repr(obj)
+ except:
+ return '(unrepresentable)'
+
+
+def short_repr(obj):
+ if isinstance(obj, (type, types.ModuleType, types.BuiltinMethodType,
+ types.BuiltinFunctionType)):
+ return obj.__name__
+ if isinstance(obj, types.MethodType):
+ if obj.im_self is not None:
+ return obj.im_func.__name__ + ' (bound)'
+ else:
+ return obj.im_func.__name__
+ if isinstance(obj, (tuple, list, dict, set)):
+ return '%d items' % len(obj)
+ if isinstance(obj, weakref.ref):
+ return 'all_weakrefs_are_one'
+ return repr(obj)[:40]
+
+
+def gradient(start_color, end_color, depth, max_depth):
+ if max_depth == 0:
+ # avoid division by zero
+ return start_color
+ h1, s1, v1 = start_color
+ h2, s2, v2 = end_color
+ f = float(depth) / max_depth
+ h = h1 * (1-f) + h2 * f
+ s = s1 * (1-f) + s2 * f
+ v = v1 * (1-f) + v2 * f
+ return h, s, v
+
+
+def edge_label(source, target):
+ if isinstance(target, dict) and target is getattr(source, '__dict__', None):
+ return ' [label="__dict__",weight=10]'
+ elif isinstance(source, dict):
+ for k, v in source.iteritems():
+ if v is target:
+ if isinstance(k, basestring) and k:
+ return ' [label="%s",weight=2]' % quote(k)
+ else:
+ return ' [label="%s"]' % quote(safe_repr(k))
+ return ''
+
+
+def program_in_path(program):
+ path = os.environ.get("PATH", os.defpath).split(os.pathsep)
+ path = [os.path.join(dir, program) for dir in path]
+ path = [True for file in path if os.path.isfile(file)]
+ return bool(path)
Added: z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/cond_perms.py
===================================================================
--- z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/cond_perms.py (rev 0)
+++ z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/cond_perms.py 2010-11-10 10:26:41 UTC (rev 118310)
@@ -0,0 +1,4 @@
+
+
+def version():
+ import pdb; pdb.set_trace()
Added: z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/configure.zcml
===================================================================
--- z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/configure.zcml (rev 0)
+++ z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/configure.zcml 2010-11-10 10:26:41 UTC (rev 118310)
@@ -0,0 +1,15 @@
+<configure
+ xmlns="http://namespaces.zope.org/zope"
+ xmlns:zcml="http://namespaces.zope.org/zcml"
+ i18n_domain="zcml">
+
+ <!--include zcml:condition="installed zope.app.component" file="configure_zope3.zcml"/-->
+ <include file="configure_zope3.zcml"/>
+
+</configure>
+
+
+
+
+
+
Added: z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/configure_zope2.zcml
===================================================================
--- z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/configure_zope2.zcml (rev 0)
+++ z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/configure_zope2.zcml 2010-11-10 10:26:41 UTC (rev 118310)
@@ -0,0 +1,140 @@
+<configure
+ xmlns="http://namespaces.zope.org/zope"
+ xmlns:browser="http://namespaces.zope.org/browser"
+ i18n_domain="z3c.memhunt.objgraph">
+
+ <browser:page
+ for="*"
+ name="ref_tools"
+ attribute="start"
+ class=".debug_views.DebugView"
+ permission="cmf.ManagePortal"
+ />
+
+ <browser:page
+ for="*"
+ name="ref_count"
+ attribute="ref_count"
+ class=".debug_views.DebugView"
+ permission="cmf.ManagePortal"
+ />
+
+ <browser:page
+ for="*"
+ name="ref_common_count"
+ attribute="most_common"
+ class=".debug_views.DebugView"
+ permission="cmf.ManagePortal"
+ />
+
+ <browser:page
+ for="*"
+ name="display_mem"
+ attribute="display_mem"
+ class=".debug_views.DebugView"
+ permission="cmf.ManagePortal"
+ />
+
+ <browser:page
+ for="*"
+ name="view_ref"
+ attribute="view_ref"
+ class=".debug_views.DebugView"
+ permission="cmf.ManagePortal"
+ />
+
+ <browser:page
+ for="*"
+ name="view_backref"
+ attribute="view_backref"
+ class=".debug_views.DebugView"
+ permission="cmf.ManagePortal"
+ />
+
+ <browser:page
+ for="*"
+ name="context_backrefs"
+ attribute="context_backrefs"
+ class=".debug_views.DebugView"
+ permission="cmf.ManagePortal"
+ />
+
+ <browser:page
+ for="*"
+ name="memory"
+ attribute="memory"
+ class=".debug_views.DebugView"
+ permission="cmf.ManagePortal"
+ />
+
+ <browser:page
+ for="*"
+ name="relative_memory"
+ attribute="relative_memory"
+ class=".debug_views.DebugView"
+ permission="cmf.ManagePortal"
+ />
+
+ <browser:page
+ for="*"
+ name="traverse_relative_memory"
+ attribute="traverse_relative_memory"
+ class=".debug_views.DebugView"
+ permission="cmf.ManagePortal"
+ />
+
+ <browser:page
+ for="*"
+ name="reset_heap"
+ attribute="reset_heap"
+ class=".debug_views.DebugView"
+ permission="cmf.ManagePortal"
+ />
+
+ <browser:page
+ for="*"
+ name="by_referrers"
+ attribute="by_referrers"
+ class=".debug_views.DebugView"
+ permission="cmf.ManagePortal"
+ />
+
+ <browser:page
+ for="*"
+ name="get_biggest_offender"
+ attribute="get_biggest_offender"
+ class=".debug_views.DebugView"
+ permission="cmf.ManagePortal"
+ />
+
+
+ <browser:page
+ for="*"
+ name="context_refs"
+ attribute="context_refs"
+ class=".debug_views.DebugView"
+ permission="cmf.ManagePortal"
+ />
+
+ <browser:page
+ for="*"
+ name="breakpoint"
+ attribute="breakpoint"
+ class=".debug_views.DebugView"
+ permission="cmf.ManagePortal"
+ />
+
+ <browser:resourceDirectory
+ name="history"
+ directory="resources/history"
+ permission="zope2.View"
+ />
+
+
+</configure>
+
+
+
+
+
+
Added: z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/configure_zope3.zcml
===================================================================
--- z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/configure_zope3.zcml (rev 0)
+++ z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/configure_zope3.zcml 2010-11-10 10:26:41 UTC (rev 118310)
@@ -0,0 +1,140 @@
+<configure
+ xmlns="http://namespaces.zope.org/zope"
+ xmlns:browser="http://namespaces.zope.org/browser"
+ i18n_domain="hc.am.base">
+
+ <browser:page
+ for="*"
+ name="ref_tools"
+ attribute="start"
+ class=".debug_views.DebugView"
+ permission="zope.ManageContent"
+ />
+
+ <browser:page
+ for="*"
+ name="ref_count"
+ attribute="ref_count"
+ class=".debug_views.DebugView"
+ permission="zope.ManageContent"
+ />
+
+ <browser:page
+ for="*"
+ name="ref_common_count"
+ attribute="most_common"
+ class=".debug_views.DebugView"
+ permission="zope.ManageContent"
+ />
+
+ <browser:page
+ for="*"
+ name="display_mem"
+ attribute="display_mem"
+ class=".debug_views.DebugView"
+ permission="zope.ManageContent"
+ />
+
+ <browser:page
+ for="*"
+ name="view_ref"
+ attribute="view_ref"
+ class=".debug_views.DebugView"
+ permission="zope.ManageContent"
+ />
+
+ <browser:page
+ for="*"
+ name="view_backref"
+ attribute="view_backref"
+ class=".debug_views.DebugView"
+ permission="zope.ManageContent"
+ />
+
+ <browser:page
+ for="*"
+ name="context_backrefs.png"
+ attribute="context_backrefs"
+ class=".debug_views.DebugView"
+ permission="zope.ManageContent"
+ />
+
+ <browser:page
+ for="*"
+ name="memory"
+ attribute="memory"
+ class=".debug_views.DebugView"
+ permission="zope.ManageContent"
+ />
+
+ <browser:page
+ for="*"
+ name="relative_memory"
+ attribute="relative_memory"
+ class=".debug_views.DebugView"
+ permission="zope.ManageContent"
+ />
+
+ <browser:page
+ for="*"
+ name="traverse_relative_memory"
+ attribute="traverse_relative_memory"
+ class=".debug_views.DebugView"
+ permission="zope.ManageContent"
+ />
+
+ <browser:page
+ for="*"
+ name="reset_heap"
+ attribute="reset_heap"
+ class=".debug_views.DebugView"
+ permission="zope.ManageContent"
+ />
+
+ <browser:page
+ for="*"
+ name="by_referrers"
+ attribute="by_referrers"
+ class=".debug_views.DebugView"
+ permission="zope.ManageContent"
+ />
+
+ <browser:page
+ for="*"
+ name="get_biggest_offender"
+ attribute="get_biggest_offender"
+ class=".debug_views.DebugView"
+ permission="zope.ManageContent"
+ />
+
+
+ <browser:page
+ for="*"
+ name="context_refs.png"
+ attribute="context_refs"
+ class=".debug_views.DebugView"
+ permission="zope.ManageContent"
+ />
+
+ <browser:page
+ for="*"
+ name="breakpoint"
+ attribute="breakpoint"
+ class=".debug_views.DebugView"
+ permission="zope.ManageContent"
+ />
+
+ <browser:resourceDirectory
+ name="history"
+ directory="resources/history"
+ permission="zope.ManageContent"
+ />
+
+ <browser:resource
+ name="debug.css"
+ file="./zpt/debug.css"
+ />
+
+
+
+</configure>
Added: z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/debug_views.py
===================================================================
--- z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/debug_views.py (rev 0)
+++ z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/debug_views.py 2010-11-10 10:26:41 UTC (rev 118310)
@@ -0,0 +1,177 @@
+import sys
+import gc
+import types
+from tempfile import NamedTemporaryFile
+
+try:
+ from Products.Five.browser import BrowserView
+ from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
+except ImportError:
+ from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
+ from zope.publisher.browser import BrowserView
+
+import checks
+import pprint
+import guppy
+
+heapy = guppy.hpy()
+
+
+class DebugView(BrowserView):
+ __allow_access_to_unprotected_subobjects__ = 1
+
+ ref_template = ViewPageTemplateFile("zpt/ref_count.pt")
+ ref_common_template = ViewPageTemplateFile("zpt/ref_common_count.pt")
+ start_template = ViewPageTemplateFile("zpt/start.pt")
+
+ evenOddClasses = ('even','odd')
+ evenodd = 0
+
+ def start(self):
+ return self.start_template(self)
+
+ def cssclass(self):
+ """ determiner what background color to use for lists """
+ if self.evenodd != 1:
+ self.evenodd = 1
+ else:
+ self.evenodd = 0
+ return self.evenOddClasses[self.evenodd]
+
+ def ref_count(self):
+ d = {}
+ # collect all classes
+ self.garbage_containing = len(gc.garbage)
+ self.garbage_watching = len(gc.get_objects())
+ self.total_ref_count = 0
+
+ for m in sys.modules.values():
+ for sym in dir(m):
+ o = getattr (m, sym)
+ if type(o) is types.ClassType:
+ d[o] = sys.getrefcount (o)
+
+ # sort by refcount
+ pairs = map (lambda x: (repr(x[0]),x[1]), d.items())
+ pairs.sort()
+ pairs.reverse()
+
+ self.pairs = []
+ for pair in pairs:
+ self.total_ref_count += pair[1]
+ self.pairs.append({'refcount':pair[1],
+ 'name':pair[0]})
+
+ self.pairs = sorted(self.pairs, key=lambda x: x['refcount'])
+ self.pairs.reverse()
+
+
+ return self.ref_template(self)
+
+ def most_common(self):
+ pairs = checks.most_common_types()
+ self.pairs = []
+ for pair in pairs:
+ self.pairs.append({'refcount':pair[1],
+ 'name':pair[0]})
+
+ return self.ref_common_template(self)
+
+ def display_mem(self):
+ import malloc_stats
+ return malloc_stats.malloc_stats()
+
+
+ @property
+ def target(self):
+ if getattr(self, '_target', None):
+ return getattr(self,'_target')
+
+ target = self.request.form.get('name','')
+ if not target:
+ return None
+
+ target = target.strip('<').strip('>')
+ target = '_'.join(target.split(' ')[:-1])
+
+ coll = {}
+ for m in sys.modules.values():
+ for sym in dir(m):
+ o = getattr (m, sym)
+ if type(o) is types.ClassType:
+ name = '_'.join(repr(o).strip('<').strip('>').split(' ')[:-1])
+ coll[name] = o
+
+ self._target = coll.get(target, None)
+ return self._target
+
+
+
+ def view_backref(self):
+ if self.target is None:
+ return "Please select an item to introspect"
+ return self.back_ref_file
+
+
+ def view_ref(self):
+ if self.target is None:
+ return "Please select an item to introspect"
+ return self.ref_file
+
+
+ @property
+ def ref_file(self):
+ self.request.response.setHeader('content-type','image/png')
+ f = NamedTemporaryFile('wb', suffix='.png')
+ checks.show_refs([self.target], max_depth=6, filename=f.name)
+ return open(f.name,'r').read()
+
+ @property
+ def back_ref_file(self):
+ self.request.response.setHeader('content-type','image/png')
+ f = NamedTemporaryFile('wb', suffix='.png')
+ checks.show_backrefs([self.target], max_depth=6, filename=f.name)
+ return open(f.name,'r').read()
+
+
+ def context_refs(self):
+ self._target = self.context
+ return self.ref_file
+
+ def context_backrefs(self):
+ self._target = self.context
+ return self.back_ref_file
+
+ def reset_heap(self):
+ # Resets for testing
+ heapy.setrelheap()
+
+ def memory(self):
+ return pprint.pformat(heapy.heap())
+
+ # Print relative memory consumption since last sycle
+ def relative_memory(self):
+ res = pprint.pformat(heapy.heap())
+ heapy.setref()
+ return res
+
+ def by_referrers(self):
+ res = pprint.pformat(heapy.heap().byrcs)
+ return res
+
+ def get_biggest_offender(self):
+ obj = heapy.heap()[0].byrcs[0].referrers.byrcs
+ res = "SIZE: %s\n\n" % obj.domisize
+ res += pprint.pformat(obj)
+ return res
+
+ # Print relative memory consumption w/heap traversing
+ def traverse_relative_memory(self):
+ res = pprint.pformat(heapy.heap().get_rp(40))
+ heapy.setref()
+ return res
+
+ def breakpoint(self):
+ import pdb; pdb.set_trace()
+ obj = heapy.heap()
+ return "done"
Added: z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/objgraph-1.py
===================================================================
--- z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/objgraph-1.py (rev 0)
+++ z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/objgraph-1.py 2010-11-10 10:26:41 UTC (rev 118310)
@@ -0,0 +1,503 @@
+"""
+Ad-hoc tools for drawing Python object reference graphs with graphviz.
+
+This module is more useful as a repository of sample code and ideas, than
+as a finished product. For documentation and background, read
+
+ http://mg.pov.lt/blog/hunting-python-memleaks.html
+ http://mg.pov.lt/blog/python-object-graphs.html
+ http://mg.pov.lt/blog/object-graphs-with-graphviz.html
+
+in that order. Then use pydoc to read the docstrings, as there were
+improvements made since those blog posts.
+
+Copyright (c) 2008-2010 Marius Gedminas <marius at pov.lt>
+
+Released under the MIT licence.
+
+
+Changes
+=======
+
+1.3.2 (unreleased)
+------------------
+
+Compatibility with Python 2.4 and 2.5 (tempfile.NamedTemporaryFile has no
+delete argument).
+
+New function: most_common_types().
+
+
+1.3.1 (2010-07-17)
+------------------
+
+Rebuild an sdist with no missing files (fixes LP#606604).
+
+Added MANIFEST.in and a Makefile to check that setup.py sdist generates
+source distributions with no files missing.
+
+
+1.3 (2010-07-13)
+----------------
+
+Highlight objects with a __del__ method.
+
+Fixes LP#483411: suggest always passing [obj] to show_refs, show_backrefs,
+since obj might be a list/tuple.
+
+Fixes LP#514422: show_refs, show_backrefs don't create files in the current
+working directory any more. Instead they accept a filename argument, which
+can be a .dot file or a .png file. If None or not specified, those functions
+will try to spawn xdot as before.
+
+New extra_info argument to graph-generating functions (patch by Thouis Jones,
+LP#558914).
+
+setup.py should work with distutils now (LP#604430, thanks to Randy Heydon).
+
+
+1.2 (2009-03-25)
+----------------
+
+Project website, public source repository, uploaded to PyPI.
+
+No code changes.
+
+
+1.1dev (2008-09-05)
+-------------------
+
+New function: show_refs() for showing forward references.
+
+New functions: typestats() and show_most_common_types().
+
+Object boxes are less crammed with useless information (such as IDs).
+
+Spawns xdot if it is available.
+"""
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+__author__ = "Marius Gedminas (marius at gedmin.as)"
+__copyright__ = "Copyright (c) 2008-2010 Marius Gedminas"
+__license__ = "MIT"
+__version__ = "1.3.2dev"
+__date__ = "2010-11-02"
+
+
+import gc
+import inspect
+import types
+import weakref
+import operator
+import os
+import subprocess
+import tempfile
+
+
+def count(typename):
+ """Count objects tracked by the garbage collector with a given class name.
+
+ Example:
+
+ >>> count('dict')
+ 42
+ >>> count('MyClass')
+ 3
+
+ Note that the GC does not track simple objects like int or str.
+ """
+ return sum(1 for o in gc.get_objects() if type(o).__name__ == typename)
+
+
+def typestats():
+ """Count the number of instances for each type tracked by the GC.
+
+ Note that the GC does not track simple objects like int or str.
+
+ Note that classes with the same name but defined in different modules
+ will be lumped together.
+ """
+ stats = {}
+ for o in gc.get_objects():
+ stats.setdefault(type(o).__name__, 0)
+ stats[type(o).__name__] += 1
+ return stats
+
+
+def most_common_types(limit=10):
+ """Count the names of types with the most instances.
+
+ Note that the GC does not track simple objects like int or str.
+
+ Note that classes with the same name but defined in different modules
+ will be lumped together.
+
+ Returns a list of (type_name, count), sorted most-frequent-first.
+ """
+ stats = sorted(typestats().items(), key=operator.itemgetter(1),
+ reverse=True)
+ if limit:
+ stats = stats[:limit]
+ return stats
+
+
+def show_most_common_types(limit=10):
+ """Print the table of types of most common instances
+
+ Note that the GC does not track simple objects like int or str.
+
+ Note that classes with the same name but defined in different modules
+ will be lumped together.
+ """
+ stats = most_common_types(limit)
+ width = max(len(name) for name, count in stats)
+ for name, count in stats:
+ print name.ljust(width), count
+
+
+def by_type(typename):
+ """Return objects tracked by the garbage collector with a given class name.
+
+ Example:
+
+ >>> by_type('MyClass')
+ [<mymodule.MyClass object at 0x...>]
+
+ Note that the GC does not track simple objects like int or str.
+ """
+ return [o for o in gc.get_objects() if type(o).__name__ == typename]
+
+
+def at(addr):
+ """Return an object at a given memory address.
+
+ The reverse of id(obj):
+
+ >>> at(id(obj)) is obj
+ True
+
+ Note that this function does not work on objects that are not tracked by
+ the GC (e.g. ints or strings).
+ """
+ for o in gc.get_objects():
+ if id(o) == addr:
+ return o
+ return None
+
+
+def find_backref_chain(obj, predicate, max_depth=20, extra_ignore=()):
+ """Find a shortest chain of references leading to obj.
+
+ The start of the chain will be some object that matches your predicate.
+
+ ``max_depth`` limits the search depth.
+
+ ``extra_ignore`` can be a list of object IDs to exclude those objects from
+ your search.
+
+ Example:
+
+ >>> find_backref_chain(obj, inspect.ismodule)
+ [<module ...>, ..., obj]
+
+ Returns None if such a chain could not be found.
+ """
+ queue = [obj]
+ depth = {id(obj): 0}
+ parent = {id(obj): None}
+ ignore = set(extra_ignore)
+ ignore.add(id(extra_ignore))
+ ignore.add(id(queue))
+ ignore.add(id(depth))
+ ignore.add(id(parent))
+ ignore.add(id(ignore))
+ gc.collect()
+ while queue:
+ target = queue.pop(0)
+ if predicate(target):
+ chain = [target]
+ while parent[id(target)] is not None:
+ target = parent[id(target)]
+ chain.append(target)
+ return chain
+ tdepth = depth[id(target)]
+ if tdepth < max_depth:
+ referrers = gc.get_referrers(target)
+ ignore.add(id(referrers))
+ for source in referrers:
+ if inspect.isframe(source) or id(source) in ignore:
+ continue
+ if id(source) not in depth:
+ depth[id(source)] = tdepth + 1
+ parent[id(source)] = target
+ queue.append(source)
+ return None # not found
+
+
+def show_backrefs(objs, max_depth=3, extra_ignore=(), filter=None, too_many=10,
+ highlight=None, filename=None, extra_info=(lambda _: '')):
+ """Generate an object reference graph ending at ``objs``
+
+ The graph will show you what objects refer to ``objs``, directly and
+ indirectly.
+
+ ``objs`` can be a single object, or it can be a list of objects. If
+ unsure, wrap the single object in a new list.
+
+ Produces a Graphviz .dot file and spawns a viewer (xdot) if one is
+ installed, otherwise converts the graph to a .png image.
+
+ Use ``max_depth`` and ``too_many`` to limit the depth and breadth of the
+ graph.
+
+ Use ``filter`` (a predicate) and ``extra_ignore`` (a list of object IDs) to
+ remove undesired objects from the graph.
+
+ Use ``highlight`` (a predicate) to highlight certain graph nodes in blue.
+
+ Use ``extra_info`` (a function returning a string) to report extra
+ information for objects.
+
+ Examples:
+
+ >>> show_backrefs(obj)
+ >>> show_backrefs([obj1, obj2])
+ >>> show_backrefs(obj, max_depth=5)
+ >>> show_backrefs(obj, filter=lambda x: not inspect.isclass(x))
+ >>> show_backrefs(obj, highlight=inspect.isclass)
+ >>> show_backrefs(obj, extra_ignore=[id(locals())])
+
+ """
+ show_graph(objs, max_depth=max_depth, extra_ignore=extra_ignore,
+ filter=filter, too_many=too_many, highlight=highlight,
+ edge_func=gc.get_referrers, swap_source_target=False,
+ filename=filename, extra_info=extra_info)
+
+
+def show_refs(objs, max_depth=3, extra_ignore=(), filter=None, too_many=10,
+ highlight=None, filename=None, extra_info=(lambda _: '')):
+ """Generate an object reference graph starting at ``objs``
+
+ The graph will show you what objects are reachable from ``objs``, directly
+ and indirectly.
+
+ ``objs`` can be a single object, or it can be a list of objects. If
+ unsure, wrap the single object in a new list.
+
+ Produces a Graphviz .dot file and spawns a viewer (xdot) if one is
+ installed, otherwise converts the graph to a .png image.
+
+ Use ``max_depth`` and ``too_many`` to limit the depth and breadth of the
+ graph.
+
+ Use ``filter`` (a predicate) and ``extra_ignore`` (a list of object IDs) to
+ remove undesired objects from the graph.
+
+ Use ``highlight`` (a predicate) to highlight certain graph nodes in blue.
+
+ Use ``extra_info`` (a function returning a string) to report extra
+ information for objects.
+
+ Examples:
+
+ >>> show_refs(obj)
+ >>> show_refs([obj1, obj2])
+ >>> show_refs(obj, max_depth=5)
+ >>> show_refs(obj, filter=lambda x: not inspect.isclass(x))
+ >>> show_refs(obj, highlight=inspect.isclass)
+ >>> show_refs(obj, extra_ignore=[id(locals())])
+
+ """
+ show_graph(objs, max_depth=max_depth, extra_ignore=extra_ignore,
+ filter=filter, too_many=too_many, highlight=highlight,
+ edge_func=gc.get_referents, swap_source_target=True,
+ filename=filename, extra_info=extra_info)
+
+#
+# Internal helpers
+#
+
+def show_graph(objs, edge_func, swap_source_target,
+ max_depth=3, extra_ignore=(), filter=None, too_many=10,
+ highlight=None, filename=None, extra_info=(lambda _: '')):
+ if not isinstance(objs, (list, tuple)):
+ objs = [objs]
+ if filename and filename.endswith('.dot'):
+ f = file(filename, 'w')
+ dot_filename = filename
+ else:
+ fd, dot_filename = tempfile.mkstemp('.dot', text=True)
+ f = os.fdopen(fd, "w")
+ print >> f, 'digraph ObjectGraph {'
+ print >> f, ' node[shape=box, style=filled, fillcolor=white];'
+ queue = []
+ depth = {}
+ ignore = set(extra_ignore)
+ ignore.add(id(objs))
+ ignore.add(id(extra_ignore))
+ ignore.add(id(queue))
+ ignore.add(id(depth))
+ ignore.add(id(ignore))
+ for obj in objs:
+ print >> f, ' %s[fontcolor=red];' % (obj_node_id(obj))
+ depth[id(obj)] = 0
+ queue.append(obj)
+ gc.collect()
+ nodes = 0
+ while queue:
+ nodes += 1
+ target = queue.pop(0)
+ tdepth = depth[id(target)]
+ print >> f, ' %s[label="%s"];' % (obj_node_id(target), obj_label(target, tdepth, extra_info))
+ h, s, v = gradient((0, 0, 1), (0, 0, .3), tdepth, max_depth)
+ if inspect.ismodule(target):
+ h = .3
+ s = 1
+ if highlight and highlight(target):
+ h = .6
+ s = .6
+ v = 0.5 + v * 0.5
+ print >> f, ' %s[fillcolor="%g,%g,%g"];' % (obj_node_id(target), h, s, v)
+ if v < 0.5:
+ print >> f, ' %s[fontcolor=white];' % (obj_node_id(target))
+ if hasattr(target, '__del__'):
+ print >> f, " %s->%s_has_a_del[color=red,style=dotted,len=0.25,weight=10];" % (obj_node_id(target), obj_node_id(target))
+ print >> f, ' %s_has_a_del[label="__del__",shape=doublecircle,height=0.25,color=red,fillcolor="0,.5,1",fontsize=6];' % (obj_node_id(target))
+ if inspect.ismodule(target) or tdepth >= max_depth:
+ continue
+ neighbours = edge_func(target)
+ ignore.add(id(neighbours))
+ n = 0
+ for source in neighbours:
+ if inspect.isframe(source) or id(source) in ignore:
+ continue
+ if filter and not filter(source):
+ continue
+ if swap_source_target:
+ srcnode, tgtnode = target, source
+ else:
+ srcnode, tgtnode = source, target
+ elabel = edge_label(srcnode, tgtnode)
+ print >> f, ' %s -> %s%s;' % (obj_node_id(srcnode), obj_node_id(tgtnode), elabel)
+ if id(source) not in depth:
+ depth[id(source)] = tdepth + 1
+ queue.append(source)
+ n += 1
+ if n >= too_many:
+ print >> f, ' %s[color=red];' % obj_node_id(target)
+ break
+ print >> f, "}"
+ f.close()
+ print "Graph written to %s (%d nodes)" % (dot_filename, nodes)
+ if filename is None and program_in_path('xdot'):
+ print "Spawning graph viewer (xdot)"
+ subprocess.Popen(['xdot', dot_filename])
+ elif program_in_path('dot'):
+ if filename is None:
+ print "Graph viewer (xdot) not found, generating a png instead"
+ if filename and filename.endswith('.png'):
+ f = file(filename, 'wb')
+ png_filename = filename
+ else:
+ if filename is not None:
+ print "Unrecognized file type (%s)" % filename
+ fd, png_filename = tempfile.mkstemp('.png', text=False)
+ f = os.fdopen(fd, "wb")
+ dot = subprocess.Popen(['dot', '-Tpng', dot_filename],
+ stdout=f)
+ dot.wait()
+ f.close()
+ print "Image generated as %s" % png_filename
+ else:
+ if filename is None:
+ print "Graph viewer (xdot) and image renderer (dot) not found, not doing anything else"
+ else:
+ print "Unrecognized file type (%s), not doing anything else" % filename
+
+
+def obj_node_id(obj):
+ if isinstance(obj, weakref.ref):
+ return 'all_weakrefs_are_one'
+ return ('o%d' % id(obj)).replace('-', '_')
+
+
+def obj_label(obj, depth, extra_info):
+ return quote(type(obj).__name__ + ':\n' +
+ safe_repr(obj) + '\n' +
+ extra_info(obj))
+
+
+def quote(s):
+ return s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n")
+
+
+def safe_repr(obj):
+ try:
+ return short_repr(obj)
+ except:
+ return '(unrepresentable)'
+
+
+def short_repr(obj):
+ if isinstance(obj, (type, types.ModuleType, types.BuiltinMethodType,
+ types.BuiltinFunctionType)):
+ return obj.__name__
+ if isinstance(obj, types.MethodType):
+ if obj.im_self is not None:
+ return obj.im_func.__name__ + ' (bound)'
+ else:
+ return obj.im_func.__name__
+ if isinstance(obj, (tuple, list, dict, set)):
+ return '%d items' % len(obj)
+ if isinstance(obj, weakref.ref):
+ return 'all_weakrefs_are_one'
+ return repr(obj)[:40]
+
+
+def gradient(start_color, end_color, depth, max_depth):
+ if max_depth == 0:
+ # avoid division by zero
+ return start_color
+ h1, s1, v1 = start_color
+ h2, s2, v2 = end_color
+ f = float(depth) / max_depth
+ h = h1 * (1-f) + h2 * f
+ s = s1 * (1-f) + s2 * f
+ v = v1 * (1-f) + v2 * f
+ return h, s, v
+
+
+def edge_label(source, target):
+ if isinstance(target, dict) and target is getattr(source, '__dict__', None):
+ return ' [label="__dict__",weight=10]'
+ elif isinstance(source, dict):
+ for k, v in source.iteritems():
+ if v is target:
+ if isinstance(k, basestring) and k:
+ return ' [label="%s",weight=2]' % quote(k)
+ else:
+ return ' [label="%s"]' % quote(safe_repr(k))
+ return ''
+
+
+def program_in_path(program):
+ path = os.environ.get("PATH", os.defpath).split(os.pathsep)
+ path = [os.path.join(dir, program) for dir in path]
+ path = [True for file in path if os.path.isfile(file)]
+ return bool(path)
Added: z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/objgraph.py
===================================================================
--- z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/objgraph.py (rev 0)
+++ z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/objgraph.py 2010-11-10 10:26:41 UTC (rev 118310)
@@ -0,0 +1,485 @@
+"""
+Ad-hoc tools for drawing Python object reference graphs with graphviz.
+
+This module is more useful as a repository of sample code and ideas, than
+as a finished product. For documentation and background, read
+
+ http://mg.pov.lt/blog/hunting-python-memleaks.html
+ http://mg.pov.lt/blog/python-object-graphs.html
+ http://mg.pov.lt/blog/object-graphs-with-graphviz.html
+
+in that order. Then use pydoc to read the docstrings, as there were
+improvements made since those blog posts.
+
+Copyright (c) 2008-2010 Marius Gedminas <marius at pov.lt>
+
+Released under the MIT licence.
+
+
+Changes
+=======
+
+1.3.2 (unreleased)
+------------------
+
+(no changes yet)
+
+
+1.3.1 (2010-07-17)
+------------------
+
+Rebuild an sdist with no missing files (fixes LP#606604)
+
+Added MANIFEST.in and a Makefile to check that setup.py sdist generates
+source distributions with no files missing.
+
+
+1.3 (2010-07-13)
+----------------
+
+Highlight objects with a __del__ method.
+
+Fixes LP#483411, LP#514422,
+
+show_refs, show_backrefs don't create files in the current working
+directory any more. Instead they accept a filename argument. It can be a
+.dot file or a .png file. If None or not specified, those functions will try
+to spawn xdot as before. Fixes LP#514422.
+
+New extra_info argument to graph-generating functions (patch by Thouis Jones,
+LP#558914).
+
+setup.py should work with distutils now (LP#604430, thanks to Randy Heydon).
+
+
+1.2 (2009-03-25)
+----------------
+
+Project website, public source repository, uploaded to PyPI.
+
+No code changes.
+
+
+1.1dev (2008-09-05)
+-------------------
+
+New function: show_refs() for showing forward references.
+
+New functions: typestats() and show_most_common_types().
+
+Object boxes are less crammed with useless information (such as IDs).
+
+Spawns xdot if it is available.
+"""
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+__author__ = "Marius Gedminas (marius at gedmin.as)"
+__copyright__ = "Copyright (c) 2008-2010 Marius Gedminas"
+__license__ = "MIT"
+__version__ = "1.3.2dev"
+__date__ = "2010-07-17"
+
+
+import gc
+import inspect
+import types
+import weakref
+import operator
+import os
+import subprocess
+from tempfile import NamedTemporaryFile
+
+
+def count(typename):
+ """Count objects tracked by the garbage collector with a given class name.
+
+ Example:
+
+ >>> count('dict')
+ 42
+ >>> count('MyClass')
+ 3
+
+ Note that the GC does not track simple objects like int or str.
+ """
+ return sum(1 for o in gc.get_objects() if type(o).__name__ == typename)
+
+
+def typestats():
+ """Count the number of instances for each type tracked by the GC.
+
+ Note that the GC does not track simple objects like int or str.
+
+ Note that classes with the same name but defined in different modules
+ will be lumped together.
+ """
+ stats = {}
+ for o in gc.get_objects():
+ stats.setdefault(type(o).__name__, 0)
+ stats[type(o).__name__] += 1
+ return stats
+
+
+def show_most_common_types(limit=10):
+ """Count the names of types with the most instances.
+
+ Note that the GC does not track simple objects like int or str.
+
+ Note that classes with the same name but defined in different modules
+ will be lumped together.
+ """
+ stats = sorted(typestats().items(), key=operator.itemgetter(1),
+ reverse=True)
+ if limit:
+ stats = stats[:limit]
+ width = max(len(name) for name, count in stats)
+ for name, count in stats[:limit]:
+ print name.ljust(width), count
+
+
+def by_type(typename):
+ """Return objects tracked by the garbage collector with a given class name.
+
+ Example:
+
+ >>> by_type('MyClass')
+ [<mymodule.MyClass object at 0x...>]
+
+ Note that the GC does not track simple objects like int or str.
+ """
+ return [o for o in gc.get_objects() if type(o).__name__ == typename]
+
+
+def at(addr):
+ """Return an object at a given memory address.
+
+ The reverse of id(obj):
+
+ >>> at(id(obj)) is obj
+ True
+
+ Note that this function does not work on objects that are not tracked by
+ the GC (e.g. ints or strings).
+ """
+ for o in gc.get_objects():
+ if id(o) == addr:
+ return o
+ return None
+
+
+def find_backref_chain(obj, predicate, max_depth=20, extra_ignore=()):
+ """Find a shortest chain of references leading to obj.
+
+ The start of the chain will be some object that matches your predicate.
+
+ ``max_depth`` limits the search depth.
+
+ ``extra_ignore`` can be a list of object IDs to exclude those objects from
+ your search.
+
+ Example:
+
+ >>> find_backref_chain(obj, inspect.ismodule)
+ [<module ...>, ..., obj]
+
+ Returns None if such a chain could not be found.
+ """
+ queue = [obj]
+ depth = {id(obj): 0}
+ parent = {id(obj): None}
+ ignore = set(extra_ignore)
+ ignore.add(id(extra_ignore))
+ ignore.add(id(queue))
+ ignore.add(id(depth))
+ ignore.add(id(parent))
+ ignore.add(id(ignore))
+ gc.collect()
+ while queue:
+ target = queue.pop(0)
+ if predicate(target):
+ chain = [target]
+ while parent[id(target)] is not None:
+ target = parent[id(target)]
+ chain.append(target)
+ return chain
+ tdepth = depth[id(target)]
+ if tdepth < max_depth:
+ referrers = gc.get_referrers(target)
+ ignore.add(id(referrers))
+ for source in referrers:
+ if inspect.isframe(source) or id(source) in ignore:
+ continue
+ if id(source) not in depth:
+ depth[id(source)] = tdepth + 1
+ parent[id(source)] = target
+ queue.append(source)
+ return None # not found
+
+
+def show_backrefs(objs, max_depth=3, extra_ignore=(), filter=None, too_many=10,
+ highlight=None, filename=None, extra_info=(lambda _: '')):
+ """Generate an object reference graph ending at ``objs``
+
+ The graph will show you what objects refer to ``objs``, directly and
+ indirectly.
+
+ ``objs`` can be a single object, or it can be a list of objects. If
+ unsure, wrap the single object in a new list.
+
+ Produces a Graphviz .dot file and spawns a viewer (xdot) if one is
+ installed, otherwise converts the graph to a .png image.
+
+ Use ``max_depth`` and ``too_many`` to limit the depth and breadth of the
+ graph.
+
+ Use ``filter`` (a predicate) and ``extra_ignore`` (a list of object IDs) to
+ remove undesired objects from the graph.
+
+ Use ``highlight`` (a predicate) to highlight certain graph nodes in blue.
+
+ Use ``extra_info`` (a function returning a string) to report extra
+ information for objects.
+
+ Examples:
+
+ >>> show_backrefs(obj)
+ >>> show_backrefs([obj1, obj2])
+ >>> show_backrefs(obj, max_depth=5)
+ >>> show_backrefs(obj, filter=lambda x: not inspect.isclass(x))
+ >>> show_backrefs(obj, highlight=inspect.isclass)
+ >>> show_backrefs(obj, extra_ignore=[id(locals())])
+
+ """
+ show_graph(objs, max_depth=max_depth, extra_ignore=extra_ignore,
+ filter=filter, too_many=too_many, highlight=highlight,
+ edge_func=gc.get_referrers, swap_source_target=False,
+ filename=filename, extra_info=extra_info)
+
+
+def show_refs(objs, max_depth=3, extra_ignore=(), filter=None, too_many=10,
+ highlight=None, filename=None, extra_info=(lambda _: '')):
+ """Generate an object reference graph starting at ``objs``
+
+ The graph will show you what objects are reachable from ``objs``, directly
+ and indirectly.
+
+ ``objs`` can be a single object, or it can be a list of objects. If
+ unsure, wrap the single object in a new list.
+
+ Produces a Graphviz .dot file and spawns a viewer (xdot) if one is
+ installed, otherwise converts the graph to a .png image.
+
+ Use ``max_depth`` and ``too_many`` to limit the depth and breadth of the
+ graph.
+
+ Use ``filter`` (a predicate) and ``extra_ignore`` (a list of object IDs) to
+ remove undesired objects from the graph.
+
+ Use ``highlight`` (a predicate) to highlight certain graph nodes in blue.
+
+ Use ``extra_info`` (a function returning a string) to report extra
+ information for objects.
+
+ Examples:
+
+ >>> show_refs(obj)
+ >>> show_refs([obj1, obj2])
+ >>> show_refs(obj, max_depth=5)
+ >>> show_refs(obj, filter=lambda x: not inspect.isclass(x))
+ >>> show_refs(obj, highlight=inspect.isclass)
+ >>> show_refs(obj, extra_ignore=[id(locals())])
+
+ """
+ show_graph(objs, max_depth=max_depth, extra_ignore=extra_ignore,
+ filter=filter, too_many=too_many, highlight=highlight,
+ edge_func=gc.get_referents, swap_source_target=True,
+ filename=filename, extra_info=extra_info)
+
+#
+# Internal helpers
+#
+
+def show_graph(objs, edge_func, swap_source_target,
+ max_depth=3, extra_ignore=(), filter=None, too_many=10,
+ highlight=None, filename=None, extra_info=(lambda _: '')):
+ if not isinstance(objs, (list, tuple)):
+ objs = [objs]
+ if filename and filename.endswith('.dot'):
+ f = file(filename, 'w')
+ dot_filename = filename
+ else:
+ f = NamedTemporaryFile('w', suffix='.dot', delete=False)
+ dot_filename = f.name
+ print >> f, 'digraph ObjectGraph {'
+ print >> f, ' node[shape=box, style=filled, fillcolor=white];'
+ queue = []
+ depth = {}
+ ignore = set(extra_ignore)
+ ignore.add(id(objs))
+ ignore.add(id(extra_ignore))
+ ignore.add(id(queue))
+ ignore.add(id(depth))
+ ignore.add(id(ignore))
+ for obj in objs:
+ print >> f, ' %s[fontcolor=red];' % (obj_node_id(obj))
+ depth[id(obj)] = 0
+ queue.append(obj)
+ gc.collect()
+ nodes = 0
+ while queue:
+ nodes += 1
+ target = queue.pop(0)
+ tdepth = depth[id(target)]
+ print >> f, ' %s[label="%s"];' % (obj_node_id(target), obj_label(target, tdepth, extra_info))
+ h, s, v = gradient((0, 0, 1), (0, 0, .3), tdepth, max_depth)
+ if inspect.ismodule(target):
+ h = .3
+ s = 1
+ if highlight and highlight(target):
+ h = .6
+ s = .6
+ v = 0.5 + v * 0.5
+ print >> f, ' %s[fillcolor="%g,%g,%g"];' % (obj_node_id(target), h, s, v)
+ if v < 0.5:
+ print >> f, ' %s[fontcolor=white];' % (obj_node_id(target))
+ if hasattr(target, '__del__'):
+ print >> f, " %s->%s_has_a_del[color=red,style=dotted,len=0.25,weight=10];" % (obj_node_id(target), obj_node_id(target))
+ print >> f, ' %s_has_a_del[label="__del__",shape=doublecircle,height=0.25,color=red,fillcolor="0,.5,1",fontsize=6];' % (obj_node_id(target))
+ if inspect.ismodule(target) or tdepth >= max_depth:
+ continue
+ neighbours = edge_func(target)
+ ignore.add(id(neighbours))
+ n = 0
+ for source in neighbours:
+ if inspect.isframe(source) or id(source) in ignore:
+ continue
+ if filter and not filter(source):
+ continue
+ if swap_source_target:
+ srcnode, tgtnode = target, source
+ else:
+ srcnode, tgtnode = source, target
+ elabel = edge_label(srcnode, tgtnode)
+ print >> f, ' %s -> %s%s;' % (obj_node_id(srcnode), obj_node_id(tgtnode), elabel)
+ if id(source) not in depth:
+ depth[id(source)] = tdepth + 1
+ queue.append(source)
+ n += 1
+ if n >= too_many:
+ print >> f, ' %s[color=red];' % obj_node_id(target)
+ break
+ print >> f, "}"
+ f.close()
+ print "Graph written to %s (%d nodes)" % (dot_filename, nodes)
+ if filename is None and program_in_path('xdot'):
+ print "Spawning graph viewer (xdot)"
+ subprocess.Popen(['xdot', dot_filename])
+ elif program_in_path('dot'):
+ if filename is None:
+ print "Graph viewer (xdot) not found, generating a png instead"
+ if filename and filename.endswith('.png'):
+ f = file(filename, 'wb')
+ png_filename = filename
+ else:
+ if filename is not None:
+ print "Unrecognized file type (%s)" % filename
+ f = NamedTemporaryFile('wb', suffix='.png', delete=False)
+ png_filename = f.name
+ dot = subprocess.Popen(['dot', '-Tpng', dot_filename],
+ stdout=f)
+ dot.wait()
+ f.close()
+ print "Image generated as %s" % png_filename
+ else:
+ if filename is None:
+ print "Graph viewer (xdot) and image renderer (dot) not found, not doing anything else"
+ else:
+ print "Unrecognized file type (%s), not doing anything else" % filename
+
+
+def obj_node_id(obj):
+ if isinstance(obj, weakref.ref):
+ return 'all_weakrefs_are_one'
+ return ('o%d' % id(obj)).replace('-', '_')
+
+
+def obj_label(obj, depth, extra_info):
+ return quote(type(obj).__name__ + ':\n' +
+ safe_repr(obj) + '\n' +
+ extra_info(obj))
+
+
+def quote(s):
+ return s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n")
+
+
+def safe_repr(obj):
+ try:
+ return short_repr(obj)
+ except:
+ return '(unrepresentable)'
+
+
+def short_repr(obj):
+ if isinstance(obj, (type, types.ModuleType, types.BuiltinMethodType,
+ types.BuiltinFunctionType)):
+ return obj.__name__
+ if isinstance(obj, types.MethodType):
+ if obj.im_self is not None:
+ return obj.im_func.__name__ + ' (bound)'
+ else:
+ return obj.im_func.__name__
+ if isinstance(obj, (tuple, list, dict, set)):
+ return '%d items' % len(obj)
+ if isinstance(obj, weakref.ref):
+ return 'all_weakrefs_are_one'
+ return repr(obj)[:40]
+
+
+def gradient(start_color, end_color, depth, max_depth):
+ if max_depth == 0:
+ # avoid division by zero
+ return start_color
+ h1, s1, v1 = start_color
+ h2, s2, v2 = end_color
+ f = float(depth) / max_depth
+ h = h1 * (1-f) + h2 * f
+ s = s1 * (1-f) + s2 * f
+ v = v1 * (1-f) + v2 * f
+ return h, s, v
+
+
+def edge_label(source, target):
+ if isinstance(target, dict) and target is getattr(source, '__dict__', None):
+ return ' [label="__dict__",weight=10]'
+ elif isinstance(source, dict):
+ for k, v in source.iteritems():
+ if v is target:
+ if isinstance(k, basestring) and k:
+ return ' [label="%s",weight=2]' % quote(k)
+ else:
+ return ' [label="%s"]' % quote(safe_repr(k))
+ return ''
+
+
+def program_in_path(program):
+ path = os.environ.get("PATH", os.defpath).split(os.pathsep)
+ path = [os.path.join(dir, program) for dir in path]
+ path = [True for file in path if os.path.isfile(file)]
+ return bool(path)
Added: z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/objgraph_orig.py
===================================================================
--- z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/objgraph_orig.py (rev 0)
+++ z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/objgraph_orig.py 2010-11-10 10:26:41 UTC (rev 118310)
@@ -0,0 +1,485 @@
+"""
+Ad-hoc tools for drawing Python object reference graphs with graphviz.
+
+This module is more useful as a repository of sample code and ideas, than
+as a finished product. For documentation and background, read
+
+ http://mg.pov.lt/blog/hunting-python-memleaks.html
+ http://mg.pov.lt/blog/python-object-graphs.html
+ http://mg.pov.lt/blog/object-graphs-with-graphviz.html
+
+in that order. Then use pydoc to read the docstrings, as there were
+improvements made since those blog posts.
+
+Copyright (c) 2008-2010 Marius Gedminas <marius at pov.lt>
+
+Released under the MIT licence.
+
+
+Changes
+=======
+
+1.3.2 (unreleased)
+------------------
+
+(no changes yet)
+
+
+1.3.1 (2010-07-17)
+------------------
+
+Rebuild an sdist with no missing files (fixes LP#606604)
+
+Added MANIFEST.in and a Makefile to check that setup.py sdist generates
+source distributions with no files missing.
+
+
+1.3 (2010-07-13)
+----------------
+
+Highlight objects with a __del__ method.
+
+Fixes LP#483411, LP#514422,
+
+show_refs, show_backrefs don't create files in the current working
+directory any more. Instead they accept a filename argument. It can be a
+.dot file or a .png file. If None or not specified, those functions will try
+to spawn xdot as before. Fixes LP#514422.
+
+New extra_info argument to graph-generating functions (patch by Thouis Jones,
+LP#558914).
+
+setup.py should work with distutils now (LP#604430, thanks to Randy Heydon).
+
+
+1.2 (2009-03-25)
+----------------
+
+Project website, public source repository, uploaded to PyPI.
+
+No code changes.
+
+
+1.1dev (2008-09-05)
+-------------------
+
+New function: show_refs() for showing forward references.
+
+New functions: typestats() and show_most_common_types().
+
+Object boxes are less crammed with useless information (such as IDs).
+
+Spawns xdot if it is available.
+"""
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+__author__ = "Marius Gedminas (marius at gedmin.as)"
+__copyright__ = "Copyright (c) 2008-2010 Marius Gedminas"
+__license__ = "MIT"
+__version__ = "1.3.2dev"
+__date__ = "2010-07-17"
+
+
+import gc
+import inspect
+import types
+import weakref
+import operator
+import os
+import subprocess
+from tempfile import NamedTemporaryFile
+
+
+def count(typename):
+ """Count objects tracked by the garbage collector with a given class name.
+
+ Example:
+
+ >>> count('dict')
+ 42
+ >>> count('MyClass')
+ 3
+
+ Note that the GC does not track simple objects like int or str.
+ """
+ return sum(1 for o in gc.get_objects() if type(o).__name__ == typename)
+
+
+def typestats():
+ """Count the number of instances for each type tracked by the GC.
+
+ Note that the GC does not track simple objects like int or str.
+
+ Note that classes with the same name but defined in different modules
+ will be lumped together.
+ """
+ stats = {}
+ for o in gc.get_objects():
+ stats.setdefault(type(o).__name__, 0)
+ stats[type(o).__name__] += 1
+ return stats
+
+
+def show_most_common_types(limit=10):
+ """Count the names of types with the most instances.
+
+ Note that the GC does not track simple objects like int or str.
+
+ Note that classes with the same name but defined in different modules
+ will be lumped together.
+ """
+ stats = sorted(typestats().items(), key=operator.itemgetter(1),
+ reverse=True)
+ if limit:
+ stats = stats[:limit]
+ width = max(len(name) for name, count in stats)
+ for name, count in stats[:limit]:
+ print name.ljust(width), count
+
+
+def by_type(typename):
+ """Return objects tracked by the garbage collector with a given class name.
+
+ Example:
+
+ >>> by_type('MyClass')
+ [<mymodule.MyClass object at 0x...>]
+
+ Note that the GC does not track simple objects like int or str.
+ """
+ return [o for o in gc.get_objects() if type(o).__name__ == typename]
+
+
+def at(addr):
+ """Return an object at a given memory address.
+
+ The reverse of id(obj):
+
+ >>> at(id(obj)) is obj
+ True
+
+ Note that this function does not work on objects that are not tracked by
+ the GC (e.g. ints or strings).
+ """
+ for o in gc.get_objects():
+ if id(o) == addr:
+ return o
+ return None
+
+
+def find_backref_chain(obj, predicate, max_depth=20, extra_ignore=()):
+ """Find a shortest chain of references leading to obj.
+
+ The start of the chain will be some object that matches your predicate.
+
+ ``max_depth`` limits the search depth.
+
+ ``extra_ignore`` can be a list of object IDs to exclude those objects from
+ your search.
+
+ Example:
+
+ >>> find_backref_chain(obj, inspect.ismodule)
+ [<module ...>, ..., obj]
+
+ Returns None if such a chain could not be found.
+ """
+ queue = [obj]
+ depth = {id(obj): 0}
+ parent = {id(obj): None}
+ ignore = set(extra_ignore)
+ ignore.add(id(extra_ignore))
+ ignore.add(id(queue))
+ ignore.add(id(depth))
+ ignore.add(id(parent))
+ ignore.add(id(ignore))
+ gc.collect()
+ while queue:
+ target = queue.pop(0)
+ if predicate(target):
+ chain = [target]
+ while parent[id(target)] is not None:
+ target = parent[id(target)]
+ chain.append(target)
+ return chain
+ tdepth = depth[id(target)]
+ if tdepth < max_depth:
+ referrers = gc.get_referrers(target)
+ ignore.add(id(referrers))
+ for source in referrers:
+ if inspect.isframe(source) or id(source) in ignore:
+ continue
+ if id(source) not in depth:
+ depth[id(source)] = tdepth + 1
+ parent[id(source)] = target
+ queue.append(source)
+ return None # not found
+
+
+def show_backrefs(objs, max_depth=3, extra_ignore=(), filter=None, too_many=10,
+ highlight=None, filename=None, extra_info=(lambda _: '')):
+ """Generate an object reference graph ending at ``objs``
+
+ The graph will show you what objects refer to ``objs``, directly and
+ indirectly.
+
+ ``objs`` can be a single object, or it can be a list of objects. If
+ unsure, wrap the single object in a new list.
+
+ Produces a Graphviz .dot file and spawns a viewer (xdot) if one is
+ installed, otherwise converts the graph to a .png image.
+
+ Use ``max_depth`` and ``too_many`` to limit the depth and breadth of the
+ graph.
+
+ Use ``filter`` (a predicate) and ``extra_ignore`` (a list of object IDs) to
+ remove undesired objects from the graph.
+
+ Use ``highlight`` (a predicate) to highlight certain graph nodes in blue.
+
+ Use ``extra_info`` (a function returning a string) to report extra
+ information for objects.
+
+ Examples:
+
+ >>> show_backrefs(obj)
+ >>> show_backrefs([obj1, obj2])
+ >>> show_backrefs(obj, max_depth=5)
+ >>> show_backrefs(obj, filter=lambda x: not inspect.isclass(x))
+ >>> show_backrefs(obj, highlight=inspect.isclass)
+ >>> show_backrefs(obj, extra_ignore=[id(locals())])
+
+ """
+ show_graph(objs, max_depth=max_depth, extra_ignore=extra_ignore,
+ filter=filter, too_many=too_many, highlight=highlight,
+ edge_func=gc.get_referrers, swap_source_target=False,
+ filename=filename, extra_info=extra_info)
+
+
+def show_refs(objs, max_depth=3, extra_ignore=(), filter=None, too_many=10,
+ highlight=None, filename=None, extra_info=(lambda _: '')):
+ """Generate an object reference graph starting at ``objs``
+
+ The graph will show you what objects are reachable from ``objs``, directly
+ and indirectly.
+
+ ``objs`` can be a single object, or it can be a list of objects. If
+ unsure, wrap the single object in a new list.
+
+ Produces a Graphviz .dot file and spawns a viewer (xdot) if one is
+ installed, otherwise converts the graph to a .png image.
+
+ Use ``max_depth`` and ``too_many`` to limit the depth and breadth of the
+ graph.
+
+ Use ``filter`` (a predicate) and ``extra_ignore`` (a list of object IDs) to
+ remove undesired objects from the graph.
+
+ Use ``highlight`` (a predicate) to highlight certain graph nodes in blue.
+
+ Use ``extra_info`` (a function returning a string) to report extra
+ information for objects.
+
+ Examples:
+
+ >>> show_refs(obj)
+ >>> show_refs([obj1, obj2])
+ >>> show_refs(obj, max_depth=5)
+ >>> show_refs(obj, filter=lambda x: not inspect.isclass(x))
+ >>> show_refs(obj, highlight=inspect.isclass)
+ >>> show_refs(obj, extra_ignore=[id(locals())])
+
+ """
+ show_graph(objs, max_depth=max_depth, extra_ignore=extra_ignore,
+ filter=filter, too_many=too_many, highlight=highlight,
+ edge_func=gc.get_referents, swap_source_target=True,
+ filename=filename, extra_info=extra_info)
+
+#
+# Internal helpers
+#
+
+def show_graph(objs, edge_func, swap_source_target,
+ max_depth=3, extra_ignore=(), filter=None, too_many=10,
+ highlight=None, filename=None, extra_info=(lambda _: '')):
+ if not isinstance(objs, (list, tuple)):
+ objs = [objs]
+ if filename and filename.endswith('.dot'):
+ f = file(filename, 'w')
+ dot_filename = filename
+ else:
+ f = NamedTemporaryFile('w', suffix='.dot', delete=False)
+ dot_filename = f.name
+ print >> f, 'digraph ObjectGraph {'
+ print >> f, ' node[shape=box, style=filled, fillcolor=white];'
+ queue = []
+ depth = {}
+ ignore = set(extra_ignore)
+ ignore.add(id(objs))
+ ignore.add(id(extra_ignore))
+ ignore.add(id(queue))
+ ignore.add(id(depth))
+ ignore.add(id(ignore))
+ for obj in objs:
+ print >> f, ' %s[fontcolor=red];' % (obj_node_id(obj))
+ depth[id(obj)] = 0
+ queue.append(obj)
+ gc.collect()
+ nodes = 0
+ while queue:
+ nodes += 1
+ target = queue.pop(0)
+ tdepth = depth[id(target)]
+ print >> f, ' %s[label="%s"];' % (obj_node_id(target), obj_label(target, tdepth, extra_info))
+ h, s, v = gradient((0, 0, 1), (0, 0, .3), tdepth, max_depth)
+ if inspect.ismodule(target):
+ h = .3
+ s = 1
+ if highlight and highlight(target):
+ h = .6
+ s = .6
+ v = 0.5 + v * 0.5
+ print >> f, ' %s[fillcolor="%g,%g,%g"];' % (obj_node_id(target), h, s, v)
+ if v < 0.5:
+ print >> f, ' %s[fontcolor=white];' % (obj_node_id(target))
+ if hasattr(target, '__del__'):
+ print >> f, " %s->%s_has_a_del[color=red,style=dotted,len=0.25,weight=10];" % (obj_node_id(target), obj_node_id(target))
+ print >> f, ' %s_has_a_del[label="__del__",shape=doublecircle,height=0.25,color=red,fillcolor="0,.5,1",fontsize=6];' % (obj_node_id(target))
+ if inspect.ismodule(target) or tdepth >= max_depth:
+ continue
+ neighbours = edge_func(target)
+ ignore.add(id(neighbours))
+ n = 0
+ for source in neighbours:
+ if inspect.isframe(source) or id(source) in ignore:
+ continue
+ if filter and not filter(source):
+ continue
+ if swap_source_target:
+ srcnode, tgtnode = target, source
+ else:
+ srcnode, tgtnode = source, target
+ elabel = edge_label(srcnode, tgtnode)
+ print >> f, ' %s -> %s%s;' % (obj_node_id(srcnode), obj_node_id(tgtnode), elabel)
+ if id(source) not in depth:
+ depth[id(source)] = tdepth + 1
+ queue.append(source)
+ n += 1
+ if n >= too_many:
+ print >> f, ' %s[color=red];' % obj_node_id(target)
+ break
+ print >> f, "}"
+ f.close()
+ print "Graph written to %s (%d nodes)" % (dot_filename, nodes)
+ if filename is None and program_in_path('xdot'):
+ print "Spawning graph viewer (xdot)"
+ subprocess.Popen(['xdot', dot_filename])
+ elif program_in_path('dot'):
+ if filename is None:
+ print "Graph viewer (xdot) not found, generating a png instead"
+ if filename and filename.endswith('.png'):
+ f = file(filename, 'wb')
+ png_filename = filename
+ else:
+ if filename is not None:
+ print "Unrecognized file type (%s)" % filename
+ f = NamedTemporaryFile('wb', suffix='.png', delete=False)
+ png_filename = f.name
+ dot = subprocess.Popen(['dot', '-Tpng', dot_filename],
+ stdout=f)
+ dot.wait()
+ f.close()
+ print "Image generated as %s" % png_filename
+ else:
+ if filename is None:
+ print "Graph viewer (xdot) and image renderer (dot) not found, not doing anything else"
+ else:
+ print "Unrecognized file type (%s), not doing anything else" % filename
+
+
+def obj_node_id(obj):
+ if isinstance(obj, weakref.ref):
+ return 'all_weakrefs_are_one'
+ return ('o%d' % id(obj)).replace('-', '_')
+
+
+def obj_label(obj, depth, extra_info):
+ return quote(type(obj).__name__ + ':\n' +
+ safe_repr(obj) + '\n' +
+ extra_info(obj))
+
+
+def quote(s):
+ return s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n")
+
+
+def safe_repr(obj):
+ try:
+ return short_repr(obj)
+ except:
+ return '(unrepresentable)'
+
+
+def short_repr(obj):
+ if isinstance(obj, (type, types.ModuleType, types.BuiltinMethodType,
+ types.BuiltinFunctionType)):
+ return obj.__name__
+ if isinstance(obj, types.MethodType):
+ if obj.im_self is not None:
+ return obj.im_func.__name__ + ' (bound)'
+ else:
+ return obj.im_func.__name__
+ if isinstance(obj, (tuple, list, dict, set)):
+ return '%d items' % len(obj)
+ if isinstance(obj, weakref.ref):
+ return 'all_weakrefs_are_one'
+ return repr(obj)[:40]
+
+
+def gradient(start_color, end_color, depth, max_depth):
+ if max_depth == 0:
+ # avoid division by zero
+ return start_color
+ h1, s1, v1 = start_color
+ h2, s2, v2 = end_color
+ f = float(depth) / max_depth
+ h = h1 * (1-f) + h2 * f
+ s = s1 * (1-f) + s2 * f
+ v = v1 * (1-f) + v2 * f
+ return h, s, v
+
+
+def edge_label(source, target):
+ if isinstance(target, dict) and target is getattr(source, '__dict__', None):
+ return ' [label="__dict__",weight=10]'
+ elif isinstance(source, dict):
+ for k, v in source.iteritems():
+ if v is target:
+ if isinstance(k, basestring) and k:
+ return ' [label="%s",weight=2]' % quote(k)
+ else:
+ return ' [label="%s"]' % quote(safe_repr(k))
+ return ''
+
+
+def program_in_path(program):
+ path = os.environ.get("PATH", os.defpath).split(os.pathsep)
+ path = [os.path.join(dir, program) for dir in path]
+ path = [True for file in path if os.path.isfile(file)]
+ return bool(path)
Added: z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/objgraph_trunk.diff
===================================================================
--- z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/objgraph_trunk.diff (rev 0)
+++ z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/objgraph_trunk.diff 2010-11-10 10:26:41 UTC (rev 118310)
@@ -0,0 +1,32 @@
+152d151
+< res = set([])
+154,155c153
+< res.add((name.ljust(width), count))
+< return res
+---
+> print name.ljust(width), count
+325,328d322
+<
+< if filename and os.path.exists(filename):
+< os.unlink(filename)
+<
+330c324
+< f = file(filename, 'wb')
+---
+> f = file(filename, 'w')
+333c327
+< f = NamedTemporaryFile('wb', suffix='.dot')
+---
+> f = NamedTemporaryFile('w', suffix='.dot', delete=False)
+335,341d328
+<
+< # NOTE:
+< # Need to do this to ensure the file is created within transaction??
+< f.close()
+< f = open(f.name, 'wb')
+< f.write('')
+<
+415c402
+< f = NamedTemporaryFile('wb', suffix='.png')
+---
+> f = NamedTemporaryFile('wb', suffix='.png', delete=False)
Added: z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/resources/history/keep.txt
===================================================================
--- z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/resources/history/keep.txt (rev 0)
+++ z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/resources/history/keep.txt 2010-11-10 10:26:41 UTC (rev 118310)
@@ -0,0 +1 @@
+#pass
\ No newline at end of file
Added: z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/resources/keep.txt
===================================================================
--- z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/resources/keep.txt (rev 0)
+++ z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/resources/keep.txt 2010-11-10 10:26:41 UTC (rev 118310)
@@ -0,0 +1 @@
+#pass
\ No newline at end of file
Added: z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/version.txt
===================================================================
--- z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/version.txt (rev 0)
+++ z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/version.txt 2010-11-10 10:26:41 UTC (rev 118310)
@@ -0,0 +1 @@
+0.1
\ No newline at end of file
Added: z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/zpt/debug.css
===================================================================
--- z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/zpt/debug.css (rev 0)
+++ z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/zpt/debug.css 2010-11-10 10:26:41 UTC (rev 118310)
@@ -0,0 +1,209 @@
+/* Normalize */
+
+body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, form, fieldset, input, p, blockquote, th, td {
+ margin : 0;
+ padding : 0;
+ font-size: 100%;
+ font-weight: normal;
+
+}
+
+/* remove borders from tables */
+
+table {
+ border-collapse : collapse;
+ border-spacing : 0;
+}
+
+/* start formatting the page */
+
+body {
+ padding: 5px;
+ font-family:Arial,sans-serif;
+ font-size:small;
+ margin: 20px;
+}
+
+.debug_body {
+ margin-left: 3em;
+}
+
+.debug_table {
+ margin-left: 40px;
+}
+
+h1 {
+ font-size: 140%;
+ font-weight: bold;
+ padding: 10px 20px;
+ width: 60%;
+ border-bottom: 1px solid #ccc;
+}
+
+.just_border {
+ padding: 0px 20px;
+}
+
+h2 {
+ font-size: 110%;
+ font-weight: bold;
+ padding: 5px 25px;
+}
+
+h3 {
+ font-weight: bold;
+ text-decoration: underline;
+ }
+
+table {
+ border: 1px solid #999;
+ width: 60%;
+}
+
+table td {
+ text-align : left;
+ vertical-align : middle;
+ font-size: 90%;
+ padding: 2px 5px;
+}
+
+* html table { /* for Internet Explorer */
+ font-size: 120%;
+}
+
+table th {
+ background-color : #ccc;
+ font-size: 90%;
+ font-weight: bolder;
+ padding:5px;
+
+}
+
+table th a {
+ text-decoration:none;
+}
+
+table th a:visited {
+ color: blue;
+}
+
+.viewname {
+ font-weight : bold;
+}
+
+.view_perm {
+ padding-left: 2em;
+ text-align: left;
+}
+
+.viewObj {
+ font-weight : bold;
+}
+
+
+.view{
+ text-align : left;
+}
+
+.permission {
+ font-style:italic;
+}
+
+.odd {
+ padding-left: 3em;
+ background-color: #ddd;
+}
+
+.even {
+ padding-left: 3em;
+}
+
+.contentList {
+ margin-left: 1em;
+}
+.contextList {
+ text-decoration: underline;
+ margin-left: 1em;
+}
+
+
+.contextListIndent {
+ margin-left: 3em;
+}
+
+.finalPermissions {
+ margin-left: 1em;
+}
+
+.sectionInfo {
+ padding-top: 5px;
+ padding-left: 5px;
+ font-size: 85%;
+ font-style:italic;
+ color: grey;
+}
+
+.legend {
+ font-style: italic;
+ /*margin-left: 25px;*/
+ font-size: 85%;
+ margin-bottom: 2em;
+
+}
+
+.description {
+ margin-right: 35%;
+ margin-left: 3em;
+}
+
+.top_nav {
+ margin-top: 5px;
+ margin-left: 38px;
+ margin-bottom: 10px;
+}
+
+.top_nav_val {
+ font-weight: bold;
+ font-size: 115%;
+}
+
+
+.headings {
+ background-color: #999;
+}
+
+.sub_heading {
+ font-size: 115%;
+ color: grey;
+ margin-bottom: 8px;
+ /*margin-left: 25px;*/
+
+}
+
+.tree{
+ float: left;
+ padding: 10px 20px;
+ margin-bottom: 8px;
+ margin-right: 8px;
+ border: 1px solid #ccc;
+
+}
+.context {
+ float: left;
+ padding: 10px 20px;
+ margin-bottom: 8px;
+ margin-right: 8px;
+ border: 1px solid #ccc;
+
+}
+
+
+/* reference link colors */
+.refs {
+ color:green;
+}
+
+.backrefs {
+ font-weight:bold;
+ color:red;
+}
Added: z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/zpt/ref_common_count.pt
===================================================================
--- z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/zpt/ref_common_count.pt (rev 0)
+++ z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/zpt/ref_common_count.pt 2010-11-10 10:26:41 UTC (rev 118310)
@@ -0,0 +1,39 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
+ xmlns:tal="http://xml.zope.org/namespaces/tal"
+ xmlns:metal="http://xml.zope.org/namespaces/metal"
+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+ lang="en"
+ i18n:domain="hc.debug.core">
+
+<body>
+
+
+
+ <div class="title">
+ <h1>Reference Count</h1>
+
+ </div>
+
+ <div>
+ <p>
+ <table summary="Most common in garbage" width="100%">
+ <thead>
+ <tr>
+ <th scope="col" class="date" nowrap="nowrap">Ref Count</th>
+ <th scope="col" class="actor">Object</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <metal:block tal:repeat="pair view/pairs">
+ <tr tal:define="oddrow repeat/pair/odd">
+ <td nowrap="nowrap" valign="top" tal:content="pair/refcount|nothing"/>
+ <td nowrap="nowrap" valign="top" tal:content="pair/name|nothing"/>
+ </tr>
+ </metal:block>
+ </tbody>
+ </table>
+
+ <a class="button" href="javascript:history.back(1)" class="back-link">Cancel</a>
+
+</html>
Added: z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/zpt/ref_count.pt
===================================================================
--- z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/zpt/ref_count.pt (rev 0)
+++ z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/zpt/ref_count.pt 2010-11-10 10:26:41 UTC (rev 118310)
@@ -0,0 +1,57 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
+ xmlns:tal="http://xml.zope.org/namespaces/tal"
+ xmlns:metal="http://xml.zope.org/namespaces/metal"
+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+ lang="en"
+ i18n:domain="hc.debug.core">
+<body>
+<head>
+ <link type="text/css" rel="stylesheet" media="all" href=""
+ tal:attributes="href string:++resource++debug.css"/>
+</head>
+
+ <metal:block tal:condition="context/title|nothing">
+ <br/><a tal:attributes="href string:."
+ tal:content="string: Back to ${context/title}"/>
+ </metal:block>
+
+ <div class="title">
+ <h1>Reference Count</h1>
+
+ </div>
+
+ <div class="debug_table">
+ <p class="debug_body description">
+ Garbage Container Objects: <span tal:replace="view/garbage_containing"/><br/>
+ Garbage Watching Objects: <span tal:replace="view/garbage_watching"/><br/>
+ Total Reference Count From Modules: <span tal:replace="view/total_ref_count"/><br/>
+
+ <table summary="Module Ref Counts" width="100%" class="debug_table">
+ <thead>
+ <tr>
+ <th scope="col" class="date" nowrap="nowrap">Ref Count</th>
+ <th scope="col" class="actor">Module</th>
+ <th scope="col" class="actor"> </th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <metal:block tal:repeat="pair view/pairs"
+ tal:attributes="class string:even">
+ <tr tal:define="oddrow repeat/pair/odd">
+ <td nowrap="nowrap" valign="top" tal:content="pair/refcount|nothing"/>
+ <td nowrap="nowrap" valign="top" tal:content="pair/name|nothing"/>
+ <td nowrap="nowrap">
+ <a tal:attributes="href string:./view_backref?name=${pair/name}"
+ tal:content="string: backrefs"/>
+ <a tal:attributes="href string:./view_ref?name=${pair/name}"
+ tal:content="string: refs"/>
+ </td>
+ </tr>
+ </metal:block>
+ </tbody>
+ </table>
+
+ <a class="button" href="javascript:history.back(1)" class="back-link">Cancel</a>
+
+</html>
Added: z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/zpt/start.pt
===================================================================
--- z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/zpt/start.pt (rev 0)
+++ z3c.memhunt.objgraph/trunk/z3c/memhunt/objgraph/zpt/start.pt 2010-11-10 10:26:41 UTC (rev 118310)
@@ -0,0 +1,83 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
+ xmlns:tal="http://xml.zope.org/namespaces/tal"
+ xmlns:metal="http://xml.zope.org/namespaces/metal"
+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+ lang="en"
+ i18n:domain="z3c.memhunt.objgraph">
+<body>
+<head>
+ <link type="text/css" rel="stylesheet" media="all" href=""
+ tal:attributes="href string:++resource++debug.css"/>
+</head>
+ <div class="debug_table">
+ <metal:block tal:condition="context/title|nothing">
+ <br/><a tal:attributes="href string:."
+ tal:content="string: Back to ${context/title}"/>
+ </metal:block>
+
+ <h1>Memory and Reference Counting Tools</h1>
+
+ <p class="description">
+ This tool is split into two logical tools. First is the Dependancy reference graphs.
+ Second is the memory introspection tools which use the guppy library for intelligence.
+ </p>
+
+ <br/>
+ <h3 class="sub_heading"> Dependancy Graph Views </h3>
+ <ul class="debug_body">
+ <li>
+ <a tal:attributes="href string:./context_refs.png"
+ tal:content="string:This Context's Reference Graph"/>
+ </li>
+
+ <li>
+ <a tal:attributes="href string:context_backrefs.png"
+ tal:content="string:This Context's Back-Reference Graph"/>
+ </li>
+
+ <li>
+ <a tal:attributes="href string:ref_count"
+ tal:content="string: View Modules Ref-Counts"/>
+ </li>
+
+ <li>
+ <a tal:attributes="href string:ref_common_count"
+ tal:content="string: View Most Common Objects in Garbage"/>
+ </li>
+
+ </ul>
+ <br/>
+ <h3 class="sub_heading"> Memory Introspection Views </h3>
+ <ul class="debug_body">
+ <li>
+ <a tal:attributes="href string:memory"
+ tal:content="string: View Memory"/>
+ </li>
+
+ <li>
+ <a tal:attributes="href string:relative_memory"
+ tal:content="string: View Relative Memory"/>
+ </li>
+
+
+ <li>
+ <a tal:attributes="href string:traverse_relative_memory"
+ tal:content="string: View Traversed Relative Memory"/>
+ </li>
+
+
+ <li>
+ <a tal:attributes="href string:by_referrers"
+ tal:content="string: View memory by Referrers"/>
+ </li>
+
+ <li>
+ <a tal:attributes="href string:get_biggest_offender"
+ tal:content="string: View Biggest Memory Offender"/>
+ </li>
+
+ </ul>
+
+ </div>
+</body>
+</html>
Added: z3c.memhunt.objgraph/trunk/z3c.memhunt.objgraph.egg-info/PKG-INFO
===================================================================
--- z3c.memhunt.objgraph/trunk/z3c.memhunt.objgraph.egg-info/PKG-INFO (rev 0)
+++ z3c.memhunt.objgraph/trunk/z3c.memhunt.objgraph.egg-info/PKG-INFO 2010-11-10 10:26:41 UTC (rev 118310)
@@ -0,0 +1,31 @@
+Metadata-Version: 1.0
+Name: z3c.memhunt.objgraph
+Version: 0.1dev
+Summary: UNKNOWN
+Home-page: ''
+Author: Daniel Blackburn & Holmes Corporation
+Author-email: danielb at holmescorp.com
+License: ZPL 2.1
+Description:
+ 0.0.1
+ -----
+ - Initial eggification of hc.debug.core
+
+ This is a global debug package used to hold debug code across any
+ site.
+
+ This package uses objgraph for introspection views
+ Change history
+ **************
+
+Keywords: zope zope2 zope3 memory objgraph graphviz guppy
+Platform: UNKNOWN
+Classifier: Development Status :: 4 - Beta
+Classifier: Environment :: Web Environment
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: Zope Public License
+Classifier: Programming Language :: Python
+Classifier: Natural Language :: English
+Classifier: Operating System :: OS Independent
+Classifier: Topic :: Internet :: WWW/HTTP
+Classifier: Framework :: Zope3
Added: z3c.memhunt.objgraph/trunk/z3c.memhunt.objgraph.egg-info/SOURCES.txt
===================================================================
--- z3c.memhunt.objgraph/trunk/z3c.memhunt.objgraph.egg-info/SOURCES.txt (rev 0)
+++ z3c.memhunt.objgraph/trunk/z3c.memhunt.objgraph.egg-info/SOURCES.txt 2010-11-10 10:26:41 UTC (rev 118310)
@@ -0,0 +1,19 @@
+README.txt
+setup.cfg
+setup.py
+z3c/__init__.py
+z3c.memhunt.objgraph.egg-info/PKG-INFO
+z3c.memhunt.objgraph.egg-info/SOURCES.txt
+z3c.memhunt.objgraph.egg-info/dependency_links.txt
+z3c.memhunt.objgraph.egg-info/namespace_packages.txt
+z3c.memhunt.objgraph.egg-info/not-zip-safe
+z3c.memhunt.objgraph.egg-info/requires.txt
+z3c.memhunt.objgraph.egg-info/top_level.txt
+z3c/memhunt/__init__.py
+z3c/memhunt/objgraph/__init__.py
+z3c/memhunt/objgraph/checks.py
+z3c/memhunt/objgraph/cond_perms.py
+z3c/memhunt/objgraph/debug_views.py
+z3c/memhunt/objgraph/objgraph-1.py
+z3c/memhunt/objgraph/objgraph.py
+z3c/memhunt/objgraph/objgraph_orig.py
\ No newline at end of file
Added: z3c.memhunt.objgraph/trunk/z3c.memhunt.objgraph.egg-info/dependency_links.txt
===================================================================
--- z3c.memhunt.objgraph/trunk/z3c.memhunt.objgraph.egg-info/dependency_links.txt (rev 0)
+++ z3c.memhunt.objgraph/trunk/z3c.memhunt.objgraph.egg-info/dependency_links.txt 2010-11-10 10:26:41 UTC (rev 118310)
@@ -0,0 +1 @@
+
Added: z3c.memhunt.objgraph/trunk/z3c.memhunt.objgraph.egg-info/namespace_packages.txt
===================================================================
--- z3c.memhunt.objgraph/trunk/z3c.memhunt.objgraph.egg-info/namespace_packages.txt (rev 0)
+++ z3c.memhunt.objgraph/trunk/z3c.memhunt.objgraph.egg-info/namespace_packages.txt 2010-11-10 10:26:41 UTC (rev 118310)
@@ -0,0 +1 @@
+z3c
Added: z3c.memhunt.objgraph/trunk/z3c.memhunt.objgraph.egg-info/not-zip-safe
===================================================================
--- z3c.memhunt.objgraph/trunk/z3c.memhunt.objgraph.egg-info/not-zip-safe (rev 0)
+++ z3c.memhunt.objgraph/trunk/z3c.memhunt.objgraph.egg-info/not-zip-safe 2010-11-10 10:26:41 UTC (rev 118310)
@@ -0,0 +1 @@
+
Added: z3c.memhunt.objgraph/trunk/z3c.memhunt.objgraph.egg-info/requires.txt
===================================================================
--- z3c.memhunt.objgraph/trunk/z3c.memhunt.objgraph.egg-info/requires.txt (rev 0)
+++ z3c.memhunt.objgraph/trunk/z3c.memhunt.objgraph.egg-info/requires.txt 2010-11-10 10:26:41 UTC (rev 118310)
@@ -0,0 +1,6 @@
+setuptools
+guppy
+objgraph
+
+[tests]
+zope.testing
\ No newline at end of file
Added: z3c.memhunt.objgraph/trunk/z3c.memhunt.objgraph.egg-info/top_level.txt
===================================================================
--- z3c.memhunt.objgraph/trunk/z3c.memhunt.objgraph.egg-info/top_level.txt (rev 0)
+++ z3c.memhunt.objgraph/trunk/z3c.memhunt.objgraph.egg-info/top_level.txt 2010-11-10 10:26:41 UTC (rev 118310)
@@ -0,0 +1 @@
+z3c
More information about the checkins
mailing list