| Index: third_party/cherrypy/lib/gctools.py
|
| ===================================================================
|
| --- third_party/cherrypy/lib/gctools.py (revision 0)
|
| +++ third_party/cherrypy/lib/gctools.py (revision 0)
|
| @@ -0,0 +1,214 @@
|
| +import gc
|
| +import inspect
|
| +import os
|
| +import sys
|
| +import time
|
| +
|
| +try:
|
| + import objgraph
|
| +except ImportError:
|
| + objgraph = None
|
| +
|
| +import cherrypy
|
| +from cherrypy import _cprequest, _cpwsgi
|
| +from cherrypy.process.plugins import SimplePlugin
|
| +
|
| +
|
| +class ReferrerTree(object):
|
| + """An object which gathers all referrers of an object to a given depth."""
|
| +
|
| + peek_length = 40
|
| +
|
| + def __init__(self, ignore=None, maxdepth=2, maxparents=10):
|
| + self.ignore = ignore or []
|
| + self.ignore.append(inspect.currentframe().f_back)
|
| + self.maxdepth = maxdepth
|
| + self.maxparents = maxparents
|
| +
|
| + def ascend(self, obj, depth=1):
|
| + """Return a nested list containing referrers of the given object."""
|
| + depth += 1
|
| + parents = []
|
| +
|
| + # Gather all referrers in one step to minimize
|
| + # cascading references due to repr() logic.
|
| + refs = gc.get_referrers(obj)
|
| + self.ignore.append(refs)
|
| + if len(refs) > self.maxparents:
|
| + return [("[%s referrers]" % len(refs), [])]
|
| +
|
| + try:
|
| + ascendcode = self.ascend.__code__
|
| + except AttributeError:
|
| + ascendcode = self.ascend.im_func.func_code
|
| + for parent in refs:
|
| + if inspect.isframe(parent) and parent.f_code is ascendcode:
|
| + continue
|
| + if parent in self.ignore:
|
| + continue
|
| + if depth <= self.maxdepth:
|
| + parents.append((parent, self.ascend(parent, depth)))
|
| + else:
|
| + parents.append((parent, []))
|
| +
|
| + return parents
|
| +
|
| + def peek(self, s):
|
| + """Return s, restricted to a sane length."""
|
| + if len(s) > (self.peek_length + 3):
|
| + half = self.peek_length // 2
|
| + return s[:half] + '...' + s[-half:]
|
| + else:
|
| + return s
|
| +
|
| + def _format(self, obj, descend=True):
|
| + """Return a string representation of a single object."""
|
| + if inspect.isframe(obj):
|
| + filename, lineno, func, context, index = inspect.getframeinfo(obj)
|
| + return "<frame of function '%s'>" % func
|
| +
|
| + if not descend:
|
| + return self.peek(repr(obj))
|
| +
|
| + if isinstance(obj, dict):
|
| + return "{" + ", ".join(["%s: %s" % (self._format(k, descend=False),
|
| + self._format(v, descend=False))
|
| + for k, v in obj.items()]) + "}"
|
| + elif isinstance(obj, list):
|
| + return "[" + ", ".join([self._format(item, descend=False)
|
| + for item in obj]) + "]"
|
| + elif isinstance(obj, tuple):
|
| + return "(" + ", ".join([self._format(item, descend=False)
|
| + for item in obj]) + ")"
|
| +
|
| + r = self.peek(repr(obj))
|
| + if isinstance(obj, (str, int, float)):
|
| + return r
|
| + return "%s: %s" % (type(obj), r)
|
| +
|
| + def format(self, tree):
|
| + """Return a list of string reprs from a nested list of referrers."""
|
| + output = []
|
| + def ascend(branch, depth=1):
|
| + for parent, grandparents in branch:
|
| + output.append((" " * depth) + self._format(parent))
|
| + if grandparents:
|
| + ascend(grandparents, depth + 1)
|
| + ascend(tree)
|
| + return output
|
| +
|
| +
|
| +def get_instances(cls):
|
| + return [x for x in gc.get_objects() if isinstance(x, cls)]
|
| +
|
| +
|
| +class RequestCounter(SimplePlugin):
|
| +
|
| + def start(self):
|
| + self.count = 0
|
| +
|
| + def before_request(self):
|
| + self.count += 1
|
| +
|
| + def after_request(self):
|
| + self.count -=1
|
| +request_counter = RequestCounter(cherrypy.engine)
|
| +request_counter.subscribe()
|
| +
|
| +
|
| +def get_context(obj):
|
| + if isinstance(obj, _cprequest.Request):
|
| + return "path=%s;stage=%s" % (obj.path_info, obj.stage)
|
| + elif isinstance(obj, _cprequest.Response):
|
| + return "status=%s" % obj.status
|
| + elif isinstance(obj, _cpwsgi.AppResponse):
|
| + return "PATH_INFO=%s" % obj.environ.get('PATH_INFO', '')
|
| + elif hasattr(obj, "tb_lineno"):
|
| + return "tb_lineno=%s" % obj.tb_lineno
|
| + return ""
|
| +
|
| +
|
| +class GCRoot(object):
|
| + """A CherryPy page handler for testing reference leaks."""
|
| +
|
| + classes = [(_cprequest.Request, 2, 2,
|
| + "Should be 1 in this request thread and 1 in the main thread."),
|
| + (_cprequest.Response, 2, 2,
|
| + "Should be 1 in this request thread and 1 in the main thread."),
|
| + (_cpwsgi.AppResponse, 1, 1,
|
| + "Should be 1 in this request thread only."),
|
| + ]
|
| +
|
| + def index(self):
|
| + return "Hello, world!"
|
| + index.exposed = True
|
| +
|
| + def stats(self):
|
| + output = ["Statistics:"]
|
| +
|
| + for trial in range(10):
|
| + if request_counter.count > 0:
|
| + break
|
| + time.sleep(0.5)
|
| + else:
|
| + output.append("\nNot all requests closed properly.")
|
| +
|
| + # gc_collect isn't perfectly synchronous, because it may
|
| + # break reference cycles that then take time to fully
|
| + # finalize. Call it thrice and hope for the best.
|
| + gc.collect()
|
| + gc.collect()
|
| + unreachable = gc.collect()
|
| + if unreachable:
|
| + if objgraph is not None:
|
| + final = objgraph.by_type('Nondestructible')
|
| + if final:
|
| + objgraph.show_backrefs(final, filename='finalizers.png')
|
| +
|
| + trash = {}
|
| + for x in gc.garbage:
|
| + trash[type(x)] = trash.get(type(x), 0) + 1
|
| + if trash:
|
| + output.insert(0, "\n%s unreachable objects:" % unreachable)
|
| + trash = [(v, k) for k, v in trash.items()]
|
| + trash.sort()
|
| + for pair in trash:
|
| + output.append(" " + repr(pair))
|
| +
|
| + # Check declared classes to verify uncollected instances.
|
| + # These don't have to be part of a cycle; they can be
|
| + # any objects that have unanticipated referrers that keep
|
| + # them from being collected.
|
| + allobjs = {}
|
| + for cls, minobj, maxobj, msg in self.classes:
|
| + allobjs[cls] = get_instances(cls)
|
| +
|
| + for cls, minobj, maxobj, msg in self.classes:
|
| + objs = allobjs[cls]
|
| + lenobj = len(objs)
|
| + if lenobj < minobj or lenobj > maxobj:
|
| + if minobj == maxobj:
|
| + output.append(
|
| + "\nExpected %s %r references, got %s." %
|
| + (minobj, cls, lenobj))
|
| + else:
|
| + output.append(
|
| + "\nExpected %s to %s %r references, got %s." %
|
| + (minobj, maxobj, cls, lenobj))
|
| +
|
| + for obj in objs:
|
| + if objgraph is not None:
|
| + ig = [id(objs), id(inspect.currentframe())]
|
| + fname = "graph_%s_%s.png" % (cls.__name__, id(obj))
|
| + objgraph.show_backrefs(
|
| + obj, extra_ignore=ig, max_depth=4, too_many=20,
|
| + filename=fname, extra_info=get_context)
|
| + output.append("\nReferrers for %s (refcount=%s):" %
|
| + (repr(obj), sys.getrefcount(obj)))
|
| + t = ReferrerTree(ignore=[objs], maxdepth=3)
|
| + tree = t.ascend(obj)
|
| + output.extend(t.format(tree))
|
| +
|
| + return "\n".join(output)
|
| + stats.exposed = True
|
| +
|
|
|
| Property changes on: third_party/cherrypy/lib/gctools.py
|
| ___________________________________________________________________
|
| Added: svn:eol-style
|
| + LF
|
|
|
|
|