~ubuntu-branches/debian/wheezy/calibre/wheezy

« back to all changes in this revision

Viewing changes to src/cherrypy/lib/gctools.py

  • Committer: Package Import Robot
  • Author(s): Martin Pitt
  • Date: 2012-01-07 11:22:54 UTC
  • mfrom: (29.4.10 precise)
  • Revision ID: package-import@ubuntu.com-20120107112254-n1syr437o46ds802
Tags: 0.8.34+dfsg-1
* New upstream version. (Closes: #654751)
* debian/rules: Do not install calibre copy of chardet; instead, add
  build/binary python-chardet dependency.
* Add disable_plugins.py: Disable plugin dialog. It uses a totally
  non-authenticated and non-trusted way of installing arbitrary code.
  (Closes: #640026)
* debian/rules: Install with POSIX locale, to avoid installing translated
  manpages into the standard locations. (Closes: #646674)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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