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