[debug] a new view to help debugging memory leaks stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Thu, 11 Mar 2010 16:49:07 +0100
branchstable
changeset 4866 550e35a69b75
parent 4865 90ad729d3540
child 4867 b67838d18a4f
[debug] a new view to help debugging memory leaks
_gcdebug.py
web/views/debug.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/_gcdebug.py	Thu Mar 11 16:49:07 2010 +0100
@@ -0,0 +1,87 @@
+
+import gc, types, weakref
+
+from cubicweb.schema import CubicWebRelationSchema, CubicWebEntitySchema
+
+listiterator = type(iter([]))
+
+IGNORE_CLASSES = (
+    type, tuple, dict, list, set, frozenset, type(len),
+    weakref.ref, weakref.WeakKeyDictionary,
+    listiterator,
+    property, classmethod,
+    types.ModuleType, types.FunctionType, types.MethodType,
+    types.MemberDescriptorType, types.GetSetDescriptorType,
+    )
+
+def _get_counted_class(obj, classes):
+    for cls in classes:
+        if isinstance(obj, cls):
+            return cls
+    raise AssertionError()
+
+def gc_info(countclasses,
+            ignoreclasses=IGNORE_CLASSES,
+            viewreferrersclasses=(), showobjs=False):
+    gc.collect()
+    gc.collect()
+    counters = {}
+    ocounters = {}
+    for obj in gc.get_objects():
+        if isinstance(obj, countclasses):
+            cls = _get_counted_class(obj, countclasses)
+            try:
+                counters[cls.__name__] += 1
+            except KeyError:
+                counters[cls.__name__] = 1
+        elif not isinstance(obj, ignoreclasses):
+            try:
+                key = '%s.%s' % (obj.__class__.__module__,
+                                 obj.__class__.__name__)
+            except AttributeError:
+                key = str(obj)
+            try:
+                ocounters[key] += 1
+            except KeyError:
+                ocounters[key] = 1
+        if isinstance(obj, viewreferrersclasses):
+            print '   ', obj, referrers(obj, showobjs)
+    return counters, ocounters, gc.garbage
+
+
+def referrers(obj, showobj=False, maxlevel=1):
+    objreferrers = _referrers(obj, maxlevel)
+    try:
+        return sorted(set((type(x), showobj and x or getattr(x, '__name__', '%#x' % id(x)))
+                          for x in objreferrers))
+    except TypeError:
+        s = set()
+        unhashable = []
+        for x in objreferrers:
+            try:
+                s.add(x)
+            except TypeError:
+                unhashable.append(x)
+        return sorted(s) + unhashable
+
+def _referrers(obj, maxlevel, _seen=None, _level=0):
+    interesting = []
+    if _seen is None:
+        _seen = set()
+    for x in gc.get_referrers(obj):
+        if id(x) in _seen:
+            continue
+        _seen.add(id(x))
+        if isinstance(x, types.FrameType):
+            continue
+        if isinstance(x, (CubicWebRelationSchema, CubicWebEntitySchema)):
+            continue
+        if isinstance(x, (list, tuple, set, dict, listiterator)):
+            if _level >= maxlevel:
+                pass
+                #interesting.append(x)
+            else:
+                interesting += _referrers(x, maxlevel, _seen, _level+1)
+        else:
+            interesting.append(x)
+    return interesting
--- a/web/views/debug.py	Thu Mar 11 16:48:38 2010 +0100
+++ b/web/views/debug.py	Thu Mar 11 16:49:07 2010 +0100
@@ -130,3 +130,40 @@
                                                   for key, val in values])
             else:
                 self.w(u'<p>Empty</p>\n')
+
+
+class GCView(StartupView):
+    """display garbage collector information"""
+    __regid__ = 'gc'
+    __select__ = StartupView.__select__ & match_user_groups('managers')
+    title = _('garbage')
+
+    def call(self, **kwargs):
+        from cubicweb._gcdebug import gc_info
+        from rql.stmts import Union
+        from cubicweb.appobject import AppObject
+        from cubicweb.rset import ResultSet
+        from cubicweb.dbapi import Connection, Cursor
+        from cubicweb.web.request import CubicWebRequestBase
+        lookupclasses = (AppObject,
+                         Union, ResultSet,
+                         Connection, Cursor,
+                         CubicWebRequestBase)
+        try:
+            from cubicweb.server.session import Session, ChildSession, InternalSession
+            lookupclasses += (InternalSession, ChildSession, Session)
+        except ImportError:
+            pass # no server part installed
+        self.w(u'<h1>%s</h1>' % _('Garbage collection information'))
+        counters, ocounters, garbage = gc_info(lookupclasses,
+                                               viewreferrersclasses=())
+        self.w(u'<h3>%s</h3>' % _('Looked up classes'))
+        values = sorted(counters.iteritems(), key=lambda x: x[1], reverse=True)
+        self.wview('pyvaltable', pyvalue=values)
+        self.w(u'<h3>%s</h3>' % _('Most referenced classes'))
+        values = sorted(ocounters.iteritems(), key=lambda x: x[1], reverse=True)
+        self.wview('pyvaltable', pyvalue=values[:self._cw.form.get('nb', 20)])
+        if garbage:
+            self.w(u'<h3>%s</h3>' % _('Unreachable objects'))
+            values = sorted(xml_escape(repr(o) for o in garbage))
+            self.wview('pyvallist', pyvalue=values)