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
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
35
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
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
89
99 ascend(tree)
100 return output
101
102
104 return [x for x in gc.get_objects() if isinstance(x, cls)]
105
106
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
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
147 return "Hello, world!"
148 index.exposed = True
149
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
161
162
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
183
184
185
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