Package cherrypy :: Package lib :: Module gctools
[hide private]
[frames] | no frames]

Source Code for Module cherrypy.lib.gctools

  1  import gc 
  2  import inspect 
  3  import os 
  4  import sys 
  5  import time 
  6   
  7  try: 
  8      import objgraph 
  9  except ImportError: 
 10      objgraph = None 
 11   
 12  import cherrypy 
 13  from cherrypy import _cprequest, _cpwsgi 
 14  from cherrypy.process.plugins import SimplePlugin 
 15   
 16   
17 -class ReferrerTree(object):
18 19 """An object which gathers all referrers of an object to a given depth.""" 20 21 peek_length = 40 22
23 - def __init__(self, ignore=None, maxdepth=2, maxparents=10):
24 self.ignore = ignore or [] 25 self.ignore.append(inspect.currentframe().f_back) 26 self.maxdepth = maxdepth 27 self.maxparents = maxparents
28
29 - def ascend(self, obj, depth=1):
30 """Return a nested list containing referrers of the given object.""" 31 depth += 1 32 parents = [] 33 34 # Gather all referrers in one step to minimize 35 # cascading references due to repr() logic. 36 refs = gc.get_referrers(obj) 37 self.ignore.append(refs) 38 if len(refs) > self.maxparents: 39 return [("[%s referrers]" % len(refs), [])] 40 41 try: 42 ascendcode = self.ascend.__code__ 43 except AttributeError: 44 ascendcode = self.ascend.im_func.func_code 45 for parent in refs: 46 if inspect.isframe(parent) and parent.f_code is ascendcode: 47 continue 48 if parent in self.ignore: 49 continue 50 if depth <= self.maxdepth: 51 parents.append((parent, self.ascend(parent, depth))) 52 else: 53 parents.append((parent, [])) 54 55 return parents
56
57 - def peek(self, s):
58 """Return s, restricted to a sane length.""" 59 if len(s) > (self.peek_length + 3): 60 half = self.peek_length // 2 61 return s[:half] + '...' + s[-half:] 62 else: 63 return s
64
65 - def _format(self, obj, descend=True):
66 """Return a string representation of a single object.""" 67 if inspect.isframe(obj): 68 filename, lineno, func, context, index = inspect.getframeinfo(obj) 69 return "<frame of function '%s'>" % func 70 71 if not descend: 72 return self.peek(repr(obj)) 73 74 if isinstance(obj, dict): 75 return "{" + ", ".join(["%s: %s" % (self._format(k, descend=False), 76 self._format(v, descend=False)) 77 for k, v in obj.items()]) + "}" 78 elif isinstance(obj, list): 79 return "[" + ", ".join([self._format(item, descend=False) 80 for item in obj]) + "]" 81 elif isinstance(obj, tuple): 82 return "(" + ", ".join([self._format(item, descend=False) 83 for item in obj]) + ")" 84 85 r = self.peek(repr(obj)) 86 if isinstance(obj, (str, int, float)): 87 return r 88 return "%s: %s" % (type(obj), r)
89
90 - def format(self, tree):
91 """Return a list of string reprs from a nested list of referrers.""" 92 output = [] 93 94 def ascend(branch, depth=1): 95 for parent, grandparents in branch: 96 output.append((" " * depth) + self._format(parent)) 97 if grandparents: 98 ascend(grandparents, depth + 1)
99 ascend(tree) 100 return output
101 102
103 -def get_instances(cls):
104 return [x for x in gc.get_objects() if isinstance(x, cls)]
105 106
107 -class RequestCounter(SimplePlugin):
108
109 - def start(self):
110 self.count = 0
111
112 - def before_request(self):
113 self.count += 1
114
115 - def after_request(self):
116 self.count -= 1
117 request_counter = RequestCounter(cherrypy.engine) 118 request_counter.subscribe() 119 120
121 -def get_context(obj):
122 if isinstance(obj, _cprequest.Request): 123 return "path=%s;stage=%s" % (obj.path_info, obj.stage) 124 elif isinstance(obj, _cprequest.Response): 125 return "status=%s" % obj.status 126 elif isinstance(obj, _cpwsgi.AppResponse): 127 return "PATH_INFO=%s" % obj.environ.get('PATH_INFO', '') 128 elif hasattr(obj, "tb_lineno"): 129 return "tb_lineno=%s" % obj.tb_lineno 130 return ""
131 132
133 -class GCRoot(object):
134 135 """A CherryPy page handler for testing reference leaks.""" 136 137 classes = [ 138 (_cprequest.Request, 2, 2, 139 "Should be 1 in this request thread and 1 in the main thread."), 140 (_cprequest.Response, 2, 2, 141 "Should be 1 in this request thread and 1 in the main thread."), 142 (_cpwsgi.AppResponse, 1, 1, 143 "Should be 1 in this request thread only."), 144 ] 145
146 - def index(self):
147 return "Hello, world!"
148 index.exposed = True 149
150 - def stats(self):
151 output = ["Statistics:"] 152 153 for trial in range(10): 154 if request_counter.count > 0: 155 break 156 time.sleep(0.5) 157 else: 158 output.append("\nNot all requests closed properly.") 159 160 # gc_collect isn't perfectly synchronous, because it may 161 # break reference cycles that then take time to fully 162 # finalize. Call it thrice and hope for the best. 163 gc.collect() 164 gc.collect() 165 unreachable = gc.collect() 166 if unreachable: 167 if objgraph is not None: 168 final = objgraph.by_type('Nondestructible') 169 if final: 170 objgraph.show_backrefs(final, filename='finalizers.png') 171 172 trash = {} 173 for x in gc.garbage: 174 trash[type(x)] = trash.get(type(x), 0) + 1 175 if trash: 176 output.insert(0, "\n%s unreachable objects:" % unreachable) 177 trash = [(v, k) for k, v in trash.items()] 178 trash.sort() 179 for pair in trash: 180 output.append(" " + repr(pair)) 181 182 # Check declared classes to verify uncollected instances. 183 # These don't have to be part of a cycle; they can be 184 # any objects that have unanticipated referrers that keep 185 # them from being collected. 186 allobjs = {} 187 for cls, minobj, maxobj, msg in self.classes: 188 allobjs[cls] = get_instances(cls) 189 190 for cls, minobj, maxobj, msg in self.classes: 191 objs = allobjs[cls] 192 lenobj = len(objs) 193 if lenobj < minobj or lenobj > maxobj: 194 if minobj == maxobj: 195 output.append( 196 "\nExpected %s %r references, got %s." % 197 (minobj, cls, lenobj)) 198 else: 199 output.append( 200 "\nExpected %s to %s %r references, got %s." % 201 (minobj, maxobj, cls, lenobj)) 202 203 for obj in objs: 204 if objgraph is not None: 205 ig = [id(objs), id(inspect.currentframe())] 206 fname = "graph_%s_%s.png" % (cls.__name__, id(obj)) 207 objgraph.show_backrefs( 208 obj, extra_ignore=ig, max_depth=4, too_many=20, 209 filename=fname, extra_info=get_context) 210 output.append("\nReferrers for %s (refcount=%s):" % 211 (repr(obj), sys.getrefcount(obj))) 212 t = ReferrerTree(ignore=[objs], maxdepth=3) 213 tree = t.ascend(obj) 214 output.extend(t.format(tree)) 215 216 return "\n".join(output)
217 stats.exposed = True
218