cavisson-pythonagent 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cavisson_pythonagent-0.0.1.dist-info/METADATA +32 -0
- cavisson_pythonagent-0.0.1.dist-info/RECORD +180 -0
- cavisson_pythonagent-0.0.1.dist-info/WHEEL +5 -0
- cavisson_pythonagent-0.0.1.dist-info/licenses/LICENSE +19 -0
- cavisson_pythonagent-0.0.1.dist-info/top_level.txt +1 -0
- pythonagent/__init__.py +22 -0
- pythonagent/agent/__init__.py +219 -0
- pythonagent/agent/internal/__init__.py +1 -0
- pythonagent/agent/internal/agent.py +1651 -0
- pythonagent/agent/internal/framesinfo.py +152 -0
- pythonagent/agent/internal/heap_dump.py +39 -0
- pythonagent/agent/internal/intercept_module.py +70 -0
- pythonagent/agent/internal/logs.py +122 -0
- pythonagent/agent/internal/metadata/__init__.py +0 -0
- pythonagent/agent/internal/metadata/agent_meta_data.py +276 -0
- pythonagent/agent/internal/proc_compat.py +75 -0
- pythonagent/agent/internal/profile.py +47 -0
- pythonagent/agent/internal/provider.py +83 -0
- pythonagent/agent/internal/thread_dump.py +85 -0
- pythonagent/agent/internal/udp.py +245 -0
- pythonagent/agent/internal/udp_message.py +800 -0
- pythonagent/agent/probes/Instrumentation/__init__.py +340 -0
- pythonagent/agent/probes/Instrumentation/find.py +243 -0
- pythonagent/agent/probes/Instrumentation/module_version_resolver.py +155 -0
- pythonagent/agent/probes/Instrumentation/new_parser.py +64 -0
- pythonagent/agent/probes/Instrumentation/parser.py +129 -0
- pythonagent/agent/probes/__init__.py +219 -0
- pythonagent/agent/probes/base.py +303 -0
- pythonagent/agent/probes/cache/__init__.py +51 -0
- pythonagent/agent/probes/cache/redis.py +119 -0
- pythonagent/agent/probes/cache/redis_asyncio.py +83 -0
- pythonagent/agent/probes/coroutines/__init__.py +1 -0
- pythonagent/agent/probes/coroutines/asyncio.py +63 -0
- pythonagent/agent/probes/elasticdb/__init__.py +7 -0
- pythonagent/agent/probes/elasticdb/aelastic.py +54 -0
- pythonagent/agent/probes/frameworks/__init__.py +27 -0
- pythonagent/agent/probes/frameworks/agentprofiler.py +105 -0
- pythonagent/agent/probes/frameworks/aiohttp_web.py +155 -0
- pythonagent/agent/probes/frameworks/aisess.py +151 -0
- pythonagent/agent/probes/frameworks/asgi.py +340 -0
- pythonagent/agent/probes/frameworks/bottle.py +27 -0
- pythonagent/agent/probes/frameworks/cherry.py +25 -0
- pythonagent/agent/probes/frameworks/django.py +128 -0
- pythonagent/agent/probes/frameworks/falcon.py +21 -0
- pythonagent/agent/probes/frameworks/fastapi.py +35 -0
- pythonagent/agent/probes/frameworks/flask.py +30 -0
- pythonagent/agent/probes/frameworks/pyramid.py +56 -0
- pythonagent/agent/probes/frameworks/test.py +108 -0
- pythonagent/agent/probes/frameworks/tornado_async_web.py +117 -0
- pythonagent/agent/probes/frameworks/tornado_web.py +133 -0
- pythonagent/agent/probes/frameworks/wsgi.py +353 -0
- pythonagent/agent/probes/grpc/__init__.py +76 -0
- pythonagent/agent/probes/grpc/client_interceptor.py +132 -0
- pythonagent/agent/probes/grpc/server_interceptor.py +129 -0
- pythonagent/agent/probes/havoc/__init__.py +0 -0
- pythonagent/agent/probes/havoc/custom_memory_stress.py +186 -0
- pythonagent/agent/probes/havoc/custom_thread_stress.py +187 -0
- pythonagent/agent/probes/havoc/havoc_constants.py +218 -0
- pythonagent/agent/probes/havoc/havoc_manager.py +981 -0
- pythonagent/agent/probes/http/__init__.py +49 -0
- pythonagent/agent/probes/http/aiohttp_client.py +59 -0
- pythonagent/agent/probes/http/boto.py +12 -0
- pythonagent/agent/probes/http/httplib.py +110 -0
- pythonagent/agent/probes/http/httpx_client.py +116 -0
- pythonagent/agent/probes/http/requests.py +15 -0
- pythonagent/agent/probes/http/tornado_httpclient.py +85 -0
- pythonagent/agent/probes/http/urllib3.py +16 -0
- pythonagent/agent/probes/langchain/__init__.py +21 -0
- pythonagent/agent/probes/langchain/base_tool.py +95 -0
- pythonagent/agent/probes/langchain/langchain_community.py +136 -0
- pythonagent/agent/probes/langchain/langchain_core.py +32 -0
- pythonagent/agent/probes/langchain/langchain_openai.py +110 -0
- pythonagent/agent/probes/logging/__init__.py +106 -0
- pythonagent/agent/probes/message_brokers/__init__.py +4 -0
- pythonagent/agent/probes/message_brokers/pika.py +126 -0
- pythonagent/agent/probes/mongodb/__init__.py +6 -0
- pythonagent/agent/probes/mongodb/pymongo.py +286 -0
- pythonagent/agent/probes/openai/__init__.py +3 -0
- pythonagent/agent/probes/openai/openai.py +797 -0
- pythonagent/agent/probes/span.py +101 -0
- pythonagent/agent/probes/sql/__init__.py +13 -0
- pythonagent/agent/probes/sql/botocores3.py +51 -0
- pythonagent/agent/probes/sql/dbapi.py +285 -0
- pythonagent/agent/probes/sql/dynamodb.py +90 -0
- pythonagent/agent/probes/sql/mysql_connector.py +24 -0
- pythonagent/agent/probes/sql/mysql_connector_cext.py +24 -0
- pythonagent/agent/probes/sql/mysqldb.py +43 -0
- pythonagent/agent/probes/sql/psycopg2.py +174 -0
- pythonagent/agent/probes/sql/pymysql.py +25 -0
- pythonagent/bootstrap/__init__.py +0 -0
- pythonagent/bootstrap/cav_gunicorn.py +26 -0
- pythonagent/bootstrap/cavagent_lambda_wrapper.py +291 -0
- pythonagent/bootstrap/run.py +47 -0
- pythonagent/bootstrap/sitecustomize.py +287 -0
- pythonagent/cavisson/netdiagnostics/CavAgent/instrumentationprofile.json +26 -0
- pythonagent/cavisson/netdiagnostics/CavAgent/interceptor_points.txt +29 -0
- pythonagent/cavisson/netdiagnostics/python/CavAgent/instrumentationprofile.json +42 -0
- pythonagent/cavisson/netdiagnostics/python/CavAgent/interceptor_points.txt +29 -0
- pythonagent/cavisson/netdiagnostics/python/config/ndsettings.conf +6 -0
- pythonagent/config.py +279 -0
- pythonagent/find.py +72 -0
- pythonagent/find_mod_cls_name.py +54 -0
- pythonagent/lang.py +131 -0
- pythonagent/lib.py +91 -0
- pythonagent/main/__init__.py +0 -0
- pythonagent/main/pytrace/__init__.py +79 -0
- pythonagent/main/pytrace/commands/__init__.py +0 -0
- pythonagent/main/pytrace/commands/auto_discovery.py +25 -0
- pythonagent/main/pytrace/commands/run.py +401 -0
- pythonagent/main/pytrace/pytrace.py +133 -0
- pythonagent/main/wsgi.py +6 -0
- pythonagent/main.py +27 -0
- pythonagent/run.py +46 -0
- pythonagent/sqins.py +8 -0
- pythonagent/test.py +73 -0
- pythonagent/utils.py +168 -0
- pythonagent/vendor/__init__.py +0 -0
- pythonagent/vendor/pympler/__init__.py +1 -0
- pythonagent/vendor/pympler/asizeof.py +2810 -0
- pythonagent/vendor/pympler/charts.py +62 -0
- pythonagent/vendor/pympler/classtracker.py +590 -0
- pythonagent/vendor/pympler/classtracker_stats.py +780 -0
- pythonagent/vendor/pympler/garbagegraph.py +80 -0
- pythonagent/vendor/pympler/mprofile.py +97 -0
- pythonagent/vendor/pympler/muppy.py +275 -0
- pythonagent/vendor/pympler/panels.py +115 -0
- pythonagent/vendor/pympler/process.py +238 -0
- pythonagent/vendor/pympler/py.typed +0 -0
- pythonagent/vendor/pympler/refbrowser.py +451 -0
- pythonagent/vendor/pympler/refgraph.py +350 -0
- pythonagent/vendor/pympler/summary.py +321 -0
- pythonagent/vendor/pympler/tracker.py +267 -0
- pythonagent/vendor/pympler/util/__init__.py +0 -0
- pythonagent/vendor/pympler/util/bottle.py +3809 -0
- pythonagent/vendor/pympler/util/compat.py +23 -0
- pythonagent/vendor/pympler/util/stringutils.py +77 -0
- pythonagent/vendor/pympler/web.py +346 -0
- pythonagent/vendor/werkzeug/__init__.py +20 -0
- pythonagent/vendor/werkzeug/_compat.py +228 -0
- pythonagent/vendor/werkzeug/_internal.py +473 -0
- pythonagent/vendor/werkzeug/_reloader.py +341 -0
- pythonagent/vendor/werkzeug/datastructures.py +3120 -0
- pythonagent/vendor/werkzeug/debug/__init__.py +498 -0
- pythonagent/vendor/werkzeug/debug/console.py +218 -0
- pythonagent/vendor/werkzeug/debug/repr.py +297 -0
- pythonagent/vendor/werkzeug/debug/tbtools.py +628 -0
- pythonagent/vendor/werkzeug/exceptions.py +829 -0
- pythonagent/vendor/werkzeug/filesystem.py +64 -0
- pythonagent/vendor/werkzeug/formparser.py +584 -0
- pythonagent/vendor/werkzeug/http.py +1307 -0
- pythonagent/vendor/werkzeug/local.py +420 -0
- pythonagent/vendor/werkzeug/middleware/__init__.py +25 -0
- pythonagent/vendor/werkzeug/middleware/dispatcher.py +66 -0
- pythonagent/vendor/werkzeug/middleware/http_proxy.py +219 -0
- pythonagent/vendor/werkzeug/middleware/lint.py +408 -0
- pythonagent/vendor/werkzeug/middleware/profiler.py +132 -0
- pythonagent/vendor/werkzeug/middleware/proxy_fix.py +169 -0
- pythonagent/vendor/werkzeug/middleware/shared_data.py +293 -0
- pythonagent/vendor/werkzeug/posixemulation.py +117 -0
- pythonagent/vendor/werkzeug/routing.py +2210 -0
- pythonagent/vendor/werkzeug/security.py +249 -0
- pythonagent/vendor/werkzeug/serving.py +1117 -0
- pythonagent/vendor/werkzeug/test.py +1123 -0
- pythonagent/vendor/werkzeug/testapp.py +241 -0
- pythonagent/vendor/werkzeug/urls.py +1138 -0
- pythonagent/vendor/werkzeug/useragents.py +202 -0
- pythonagent/vendor/werkzeug/utils.py +778 -0
- pythonagent/vendor/werkzeug/wrappers/__init__.py +36 -0
- pythonagent/vendor/werkzeug/wrappers/accept.py +50 -0
- pythonagent/vendor/werkzeug/wrappers/auth.py +33 -0
- pythonagent/vendor/werkzeug/wrappers/base_request.py +673 -0
- pythonagent/vendor/werkzeug/wrappers/base_response.py +700 -0
- pythonagent/vendor/werkzeug/wrappers/common_descriptors.py +341 -0
- pythonagent/vendor/werkzeug/wrappers/cors.py +100 -0
- pythonagent/vendor/werkzeug/wrappers/etag.py +304 -0
- pythonagent/vendor/werkzeug/wrappers/json.py +145 -0
- pythonagent/vendor/werkzeug/wrappers/request.py +49 -0
- pythonagent/vendor/werkzeug/wrappers/response.py +84 -0
- pythonagent/vendor/werkzeug/wrappers/user_agent.py +14 -0
- pythonagent/vendor/werkzeug/wsgi.py +1000 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module exposes utilities to illustrate objects and their references as
|
|
3
|
+
(directed) graphs. The current implementation requires 'graphviz' to be
|
|
4
|
+
installed.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .asizeof import Asizer, named_refs
|
|
8
|
+
from .util.stringutils import safe_repr, trunc
|
|
9
|
+
from gc import get_referents
|
|
10
|
+
from subprocess import Popen, PIPE
|
|
11
|
+
from copy import copy
|
|
12
|
+
from sys import platform
|
|
13
|
+
|
|
14
|
+
__all__ = ['ReferenceGraph']
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# Popen might lead to deadlocks when file descriptors are leaked to
|
|
18
|
+
# sub-processes on Linux. On Windows, however, close_fds=True leads to
|
|
19
|
+
# ValueError if stdin/stdout/stderr is piped:
|
|
20
|
+
# http://code.google.com/p/pympler/issues/detail?id=28#c1
|
|
21
|
+
popen_flags = {}
|
|
22
|
+
if platform not in ['win32']: # pragma: no branch
|
|
23
|
+
popen_flags['close_fds'] = True
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class _MetaObject(object):
|
|
27
|
+
"""
|
|
28
|
+
The _MetaObject stores meta-information, like a string representation,
|
|
29
|
+
corresponding to each object passed to a ReferenceGraph.
|
|
30
|
+
"""
|
|
31
|
+
__slots__ = ('size', 'id', 'type', 'str', 'group', 'cycle')
|
|
32
|
+
|
|
33
|
+
def __init__(self):
|
|
34
|
+
self.cycle = False
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class _Edge(object):
|
|
38
|
+
"""
|
|
39
|
+
Describes a reference from one object `src` to another object `dst`.
|
|
40
|
+
"""
|
|
41
|
+
__slots__ = ('src', 'dst', 'label', 'group')
|
|
42
|
+
|
|
43
|
+
def __init__(self, src, dst, label):
|
|
44
|
+
self.src = src
|
|
45
|
+
self.dst = dst
|
|
46
|
+
self.label = label
|
|
47
|
+
self.group = None
|
|
48
|
+
|
|
49
|
+
def __repr__(self):
|
|
50
|
+
return "<%08x => %08x, '%s', %s>" % (self.src, self.dst, self.label,
|
|
51
|
+
self.group)
|
|
52
|
+
|
|
53
|
+
def __hash__(self):
|
|
54
|
+
return (self.src, self.dst, self.label).__hash__()
|
|
55
|
+
|
|
56
|
+
def __eq__(self, other):
|
|
57
|
+
return self.__hash__() == other.__hash__()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class ReferenceGraph(object):
|
|
61
|
+
"""
|
|
62
|
+
The ReferenceGraph illustrates the references between a collection of
|
|
63
|
+
objects by rendering a directed graph. That requires that 'graphviz' is
|
|
64
|
+
installed.
|
|
65
|
+
|
|
66
|
+
>>> from pympler.refgraph import ReferenceGraph
|
|
67
|
+
>>> a = 42
|
|
68
|
+
>>> b = 'spam'
|
|
69
|
+
>>> c = {a: b}
|
|
70
|
+
>>> gb = ReferenceGraph([a,b,c])
|
|
71
|
+
>>> gb.render('spam.eps')
|
|
72
|
+
True
|
|
73
|
+
"""
|
|
74
|
+
def __init__(self, objects, reduce=False):
|
|
75
|
+
"""
|
|
76
|
+
Initialize the ReferenceGraph with a collection of `objects`.
|
|
77
|
+
"""
|
|
78
|
+
self.objects = list(objects)
|
|
79
|
+
self.count = len(self.objects)
|
|
80
|
+
self.num_in_cycles = 'N/A'
|
|
81
|
+
self.edges = None
|
|
82
|
+
|
|
83
|
+
if reduce:
|
|
84
|
+
self.num_in_cycles = self._reduce_to_cycles()
|
|
85
|
+
self._reduced = self # TODO: weakref?
|
|
86
|
+
else:
|
|
87
|
+
self._reduced = None
|
|
88
|
+
|
|
89
|
+
self._get_edges()
|
|
90
|
+
self._annotate_objects()
|
|
91
|
+
|
|
92
|
+
def _eliminate_leafs(self, graph):
|
|
93
|
+
"""
|
|
94
|
+
Eliminate leaf objects - that are objects not referencing any other
|
|
95
|
+
objects in the list `graph`. Returns the list of objects without the
|
|
96
|
+
objects identified as leafs.
|
|
97
|
+
"""
|
|
98
|
+
result = []
|
|
99
|
+
idset = set([id(x) for x in graph])
|
|
100
|
+
for n in graph:
|
|
101
|
+
refset = set([id(x) for x in get_referents(n)])
|
|
102
|
+
if refset.intersection(idset):
|
|
103
|
+
result.append(n)
|
|
104
|
+
return result
|
|
105
|
+
|
|
106
|
+
def _reduce_to_cycles(self):
|
|
107
|
+
"""
|
|
108
|
+
Iteratively eliminate leafs to reduce the set of objects to only those
|
|
109
|
+
that build cycles. Return the number of objects involved in reference
|
|
110
|
+
cycles. If there are no cycles, `self.objects` will be an empty list
|
|
111
|
+
and this method returns 0.
|
|
112
|
+
"""
|
|
113
|
+
cycles = self.objects[:]
|
|
114
|
+
cnt = 0
|
|
115
|
+
while cnt != len(cycles):
|
|
116
|
+
cnt = len(cycles)
|
|
117
|
+
cycles = self._eliminate_leafs(cycles)
|
|
118
|
+
self.objects = cycles
|
|
119
|
+
return len(self.objects)
|
|
120
|
+
|
|
121
|
+
def reduce_to_cycles(self):
|
|
122
|
+
"""
|
|
123
|
+
Iteratively eliminate leafs to reduce the set of objects to only those
|
|
124
|
+
that build cycles. Return the reduced graph. If there are no cycles,
|
|
125
|
+
None is returned.
|
|
126
|
+
"""
|
|
127
|
+
if not self._reduced:
|
|
128
|
+
reduced = copy(self)
|
|
129
|
+
reduced.objects = self.objects[:]
|
|
130
|
+
reduced.metadata = []
|
|
131
|
+
reduced.edges = []
|
|
132
|
+
self.num_in_cycles = reduced._reduce_to_cycles()
|
|
133
|
+
reduced.num_in_cycles = self.num_in_cycles
|
|
134
|
+
if self.num_in_cycles:
|
|
135
|
+
reduced._get_edges()
|
|
136
|
+
reduced._annotate_objects()
|
|
137
|
+
for meta in reduced.metadata:
|
|
138
|
+
meta.cycle = True
|
|
139
|
+
else:
|
|
140
|
+
reduced = None
|
|
141
|
+
self._reduced = reduced
|
|
142
|
+
return self._reduced
|
|
143
|
+
|
|
144
|
+
def _get_edges(self):
|
|
145
|
+
"""
|
|
146
|
+
Compute the edges for the reference graph.
|
|
147
|
+
The function returns a set of tuples (id(a), id(b), ref) if a
|
|
148
|
+
references b with the referent 'ref'.
|
|
149
|
+
"""
|
|
150
|
+
idset = set([id(x) for x in self.objects])
|
|
151
|
+
self.edges = set([])
|
|
152
|
+
for n in self.objects:
|
|
153
|
+
refset = set([id(x) for x in get_referents(n)])
|
|
154
|
+
for ref in refset.intersection(idset):
|
|
155
|
+
label = ''
|
|
156
|
+
members = None
|
|
157
|
+
if isinstance(n, dict):
|
|
158
|
+
members = n.items()
|
|
159
|
+
if not members:
|
|
160
|
+
members = named_refs(n)
|
|
161
|
+
for (k, v) in members:
|
|
162
|
+
if id(v) == ref:
|
|
163
|
+
label = k
|
|
164
|
+
break
|
|
165
|
+
self.edges.add(_Edge(id(n), ref, label))
|
|
166
|
+
|
|
167
|
+
def _annotate_groups(self):
|
|
168
|
+
"""
|
|
169
|
+
Annotate the objects belonging to separate (non-connected) graphs with
|
|
170
|
+
individual indices.
|
|
171
|
+
"""
|
|
172
|
+
g = {}
|
|
173
|
+
for x in self.metadata:
|
|
174
|
+
g[x.id] = x
|
|
175
|
+
|
|
176
|
+
idx = 0
|
|
177
|
+
for x in self.metadata:
|
|
178
|
+
if not hasattr(x, 'group'):
|
|
179
|
+
x.group = idx
|
|
180
|
+
idx += 1
|
|
181
|
+
neighbors = set()
|
|
182
|
+
for e in self.edges:
|
|
183
|
+
if e.src == x.id:
|
|
184
|
+
neighbors.add(e.dst)
|
|
185
|
+
if e.dst == x.id:
|
|
186
|
+
neighbors.add(e.src)
|
|
187
|
+
for nb in neighbors:
|
|
188
|
+
g[nb].group = min(x.group, getattr(g[nb], 'group', idx))
|
|
189
|
+
|
|
190
|
+
# Assign the edges to the respective groups. Both "ends" of the edge
|
|
191
|
+
# should share the same group so just use the first object's group.
|
|
192
|
+
for e in self.edges:
|
|
193
|
+
e.group = g[e.src].group
|
|
194
|
+
|
|
195
|
+
self._max_group = idx
|
|
196
|
+
|
|
197
|
+
def _filter_group(self, group):
|
|
198
|
+
"""
|
|
199
|
+
Eliminate all objects but those which belong to `group`.
|
|
200
|
+
``self.objects``, ``self.metadata`` and ``self.edges`` are modified.
|
|
201
|
+
Returns `True` if the group is non-empty. Otherwise returns `False`.
|
|
202
|
+
"""
|
|
203
|
+
self.metadata = [x for x in self.metadata if x.group == group]
|
|
204
|
+
group_set = set([x.id for x in self.metadata])
|
|
205
|
+
self.objects = [obj for obj in self.objects if id(obj) in group_set]
|
|
206
|
+
self.count = len(self.metadata)
|
|
207
|
+
if self.metadata == []:
|
|
208
|
+
return False
|
|
209
|
+
|
|
210
|
+
self.edges = [e for e in self.edges if e.group == group]
|
|
211
|
+
|
|
212
|
+
del self._max_group
|
|
213
|
+
|
|
214
|
+
return True
|
|
215
|
+
|
|
216
|
+
def split(self):
|
|
217
|
+
"""
|
|
218
|
+
Split the graph into sub-graphs. Only connected objects belong to the
|
|
219
|
+
same graph. `split` yields copies of the Graph object. Shallow copies
|
|
220
|
+
are used that only replicate the meta-information, but share the same
|
|
221
|
+
object list ``self.objects``.
|
|
222
|
+
|
|
223
|
+
>>> from pympler.refgraph import ReferenceGraph
|
|
224
|
+
>>> a = 42
|
|
225
|
+
>>> b = 'spam'
|
|
226
|
+
>>> c = {a: b}
|
|
227
|
+
>>> t = (1,2,3)
|
|
228
|
+
>>> rg = ReferenceGraph([a,b,c,t])
|
|
229
|
+
>>> for subgraph in rg.split():
|
|
230
|
+
... print (subgraph.index)
|
|
231
|
+
0
|
|
232
|
+
1
|
|
233
|
+
"""
|
|
234
|
+
self._annotate_groups()
|
|
235
|
+
index = 0
|
|
236
|
+
|
|
237
|
+
for group in range(self._max_group):
|
|
238
|
+
subgraph = copy(self)
|
|
239
|
+
subgraph.metadata = self.metadata[:]
|
|
240
|
+
subgraph.edges = self.edges.copy()
|
|
241
|
+
|
|
242
|
+
if subgraph._filter_group(group):
|
|
243
|
+
subgraph.total_size = sum([x.size for x in subgraph.metadata])
|
|
244
|
+
subgraph.index = index
|
|
245
|
+
index += 1
|
|
246
|
+
yield subgraph
|
|
247
|
+
|
|
248
|
+
def split_and_sort(self):
|
|
249
|
+
"""
|
|
250
|
+
Split the graphs into sub graphs and return a list of all graphs sorted
|
|
251
|
+
by the number of nodes. The graph with most nodes is returned first.
|
|
252
|
+
"""
|
|
253
|
+
graphs = list(self.split())
|
|
254
|
+
graphs.sort(key=lambda x: -len(x.metadata))
|
|
255
|
+
for index, graph in enumerate(graphs):
|
|
256
|
+
graph.index = index
|
|
257
|
+
return graphs
|
|
258
|
+
|
|
259
|
+
def _annotate_objects(self):
|
|
260
|
+
"""
|
|
261
|
+
Extract meta-data describing the stored objects.
|
|
262
|
+
"""
|
|
263
|
+
self.metadata = []
|
|
264
|
+
sizer = Asizer()
|
|
265
|
+
sizes = sizer.asizesof(*self.objects)
|
|
266
|
+
self.total_size = sizer.total
|
|
267
|
+
for obj, sz in zip(self.objects, sizes):
|
|
268
|
+
md = _MetaObject()
|
|
269
|
+
md.size = sz
|
|
270
|
+
md.id = id(obj)
|
|
271
|
+
try:
|
|
272
|
+
md.type = obj.__class__.__name__
|
|
273
|
+
except (AttributeError, ReferenceError): # pragma: no cover
|
|
274
|
+
md.type = type(obj).__name__
|
|
275
|
+
md.str = safe_repr(obj, clip=128)
|
|
276
|
+
self.metadata.append(md)
|
|
277
|
+
|
|
278
|
+
def _get_graphviz_data(self):
|
|
279
|
+
"""
|
|
280
|
+
Emit a graph representing the connections between the objects described
|
|
281
|
+
within the metadata list. The text representation can be transformed to
|
|
282
|
+
a graph with graphviz. Returns a string.
|
|
283
|
+
"""
|
|
284
|
+
s = []
|
|
285
|
+
header = '// Process this file with graphviz\n'
|
|
286
|
+
s.append(header)
|
|
287
|
+
s.append('digraph G {\n')
|
|
288
|
+
s.append(' node [shape=box];\n')
|
|
289
|
+
for md in self.metadata:
|
|
290
|
+
label = trunc(md.str, 48).replace('"', "'")
|
|
291
|
+
extra = ''
|
|
292
|
+
if md.type == 'instancemethod':
|
|
293
|
+
extra = ', color=red'
|
|
294
|
+
elif md.type == 'frame':
|
|
295
|
+
extra = ', color=orange'
|
|
296
|
+
s.append(' "X%s" [ label = "%s\\n%s" %s ];\n' %
|
|
297
|
+
(hex(md.id)[1:], label, md.type, extra))
|
|
298
|
+
for e in self.edges:
|
|
299
|
+
extra = ''
|
|
300
|
+
if e.label == '__dict__':
|
|
301
|
+
extra = ',weight=100'
|
|
302
|
+
s.append(' X%s -> X%s [label="%s"%s];\n' %
|
|
303
|
+
(hex(e.src)[1:], hex(e.dst)[1:], e.label, extra))
|
|
304
|
+
|
|
305
|
+
s.append('}\n')
|
|
306
|
+
return "".join(s)
|
|
307
|
+
|
|
308
|
+
def render(self, filename, cmd='dot', format='ps', unflatten=False):
|
|
309
|
+
"""
|
|
310
|
+
Render the graph to `filename` using graphviz. The graphviz invocation
|
|
311
|
+
command may be overridden by specifying `cmd`. The `format` may be any
|
|
312
|
+
specifier recognized by the graph renderer ('-Txxx' command). The
|
|
313
|
+
graph can be preprocessed by the *unflatten* tool if the `unflatten`
|
|
314
|
+
parameter is True. If there are no objects to illustrate, the method
|
|
315
|
+
does not invoke graphviz and returns False. If the renderer returns
|
|
316
|
+
successfully (return code 0), True is returned.
|
|
317
|
+
|
|
318
|
+
An `OSError` is raised if the graphviz tool cannot be found.
|
|
319
|
+
"""
|
|
320
|
+
if self.objects == []:
|
|
321
|
+
return False
|
|
322
|
+
|
|
323
|
+
data = self._get_graphviz_data()
|
|
324
|
+
|
|
325
|
+
options = ('-Nfontsize=10',
|
|
326
|
+
'-Efontsize=10',
|
|
327
|
+
'-Nstyle=filled',
|
|
328
|
+
'-Nfillcolor=#E5EDB8',
|
|
329
|
+
'-Ncolor=#CCCCCC')
|
|
330
|
+
cmdline = (cmd, '-T%s' % format, '-o', filename) + options
|
|
331
|
+
|
|
332
|
+
if unflatten:
|
|
333
|
+
p1 = Popen(('unflatten', '-l7'), stdin=PIPE, stdout=PIPE,
|
|
334
|
+
**popen_flags)
|
|
335
|
+
p2 = Popen(cmdline, stdin=p1.stdout, **popen_flags)
|
|
336
|
+
p1.communicate(data.encode())
|
|
337
|
+
p2.communicate()
|
|
338
|
+
return p2.returncode == 0
|
|
339
|
+
else:
|
|
340
|
+
p = Popen(cmdline, stdin=PIPE, **popen_flags)
|
|
341
|
+
p.communicate(data.encode())
|
|
342
|
+
return p.returncode == 0
|
|
343
|
+
|
|
344
|
+
def write_graph(self, filename):
|
|
345
|
+
"""
|
|
346
|
+
Write raw graph data which can be post-processed using graphviz.
|
|
347
|
+
"""
|
|
348
|
+
f = open(filename, 'w')
|
|
349
|
+
f.write(self._get_graphviz_data())
|
|
350
|
+
f.close()
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
"""A collection of functions to summarize object information.
|
|
2
|
+
|
|
3
|
+
This module provides several function which will help you to analyze object
|
|
4
|
+
information which was gathered. Often it is sufficient to work with aggregated
|
|
5
|
+
data instead of handling the entire set of existing objects. For example can a
|
|
6
|
+
memory leak identified simple based on the number and size of existing objects.
|
|
7
|
+
|
|
8
|
+
A summary contains information about objects in a table-like manner.
|
|
9
|
+
Technically, it is a list of lists. Each of these lists represents a row,
|
|
10
|
+
whereas the first column reflects the object type, the second column the number
|
|
11
|
+
of objects, and the third column the size of all these objects. This allows a
|
|
12
|
+
simple table-like output like the following:
|
|
13
|
+
|
|
14
|
+
============= ============ =============
|
|
15
|
+
types # objects total size
|
|
16
|
+
============= ============ =============
|
|
17
|
+
<type 'dict'> 2 560
|
|
18
|
+
<type 'str'> 3 126
|
|
19
|
+
<type 'int'> 4 96
|
|
20
|
+
<type 'long'> 2 66
|
|
21
|
+
<type 'list'> 1 40
|
|
22
|
+
============= ============ =============
|
|
23
|
+
|
|
24
|
+
Another advantage of summaries is that they influence the system you analyze
|
|
25
|
+
only to a minimum. Working with references to existing objects will keep these
|
|
26
|
+
objects alive. Most of the times this is no desired behavior (as it will have
|
|
27
|
+
an impact on the observations). Using summaries reduces this effect greatly.
|
|
28
|
+
|
|
29
|
+
output representation
|
|
30
|
+
---------------------
|
|
31
|
+
|
|
32
|
+
The output representation of types is defined in summary.representations.
|
|
33
|
+
Every type defined in this dictionary will be represented as specified. Each
|
|
34
|
+
definition has a list of different representations. The later a representation
|
|
35
|
+
appears in this list, the higher its verbosity level. From types which are not
|
|
36
|
+
defined in summary.representations the default str() representation will be
|
|
37
|
+
used.
|
|
38
|
+
|
|
39
|
+
Per default, summaries will use the verbosity level 1 for any encountered type.
|
|
40
|
+
The reason is that several computations are done with summaries and rows have
|
|
41
|
+
to remain comparable. Therefore information which reflect an objects state,
|
|
42
|
+
e.g. the current line number of a frame, should not be included. You may add
|
|
43
|
+
more detailed information at higher verbosity levels than 1.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
import re
|
|
47
|
+
import sys
|
|
48
|
+
import types
|
|
49
|
+
|
|
50
|
+
from .util import stringutils
|
|
51
|
+
from sys import getsizeof
|
|
52
|
+
|
|
53
|
+
representations = {}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _init_representations():
|
|
57
|
+
global representations
|
|
58
|
+
if sys.hexversion < 0x2040000:
|
|
59
|
+
classobj = [
|
|
60
|
+
lambda c: "classobj(%s)" % repr(c),
|
|
61
|
+
]
|
|
62
|
+
representations[types.ClassType] = classobj
|
|
63
|
+
instance = [
|
|
64
|
+
lambda f: "instance(%s)" % repr(f.__class__),
|
|
65
|
+
]
|
|
66
|
+
representations[types.InstanceType] = instance
|
|
67
|
+
instancemethod = [
|
|
68
|
+
lambda i: "instancemethod (%s)" % (repr(i.im_func)),
|
|
69
|
+
lambda i: "instancemethod (%s, %s)" % (repr(i.im_class),
|
|
70
|
+
repr(i.im_func)),
|
|
71
|
+
]
|
|
72
|
+
representations[types.MethodType] = instancemethod
|
|
73
|
+
frame = [
|
|
74
|
+
lambda f: "frame (codename: %s)" % (f.f_code.co_name),
|
|
75
|
+
lambda f: "frame (codename: %s, codeline: %s)" %
|
|
76
|
+
(f.f_code.co_name, f.f_code.co_firstlineno),
|
|
77
|
+
lambda f: "frame (codename: %s, filename: %s, codeline: %s)" %
|
|
78
|
+
(f.f_code.co_name, f.f_code.co_filename,
|
|
79
|
+
f.f_code.co_firstlineno)
|
|
80
|
+
]
|
|
81
|
+
representations[types.FrameType] = frame
|
|
82
|
+
_dict = [
|
|
83
|
+
lambda d: str(type(d)),
|
|
84
|
+
lambda d: "dict, len=%s" % len(d),
|
|
85
|
+
]
|
|
86
|
+
representations[dict] = _dict
|
|
87
|
+
function = [
|
|
88
|
+
lambda f: "function (%s)" % f.__name__,
|
|
89
|
+
lambda f: "function (%s.%s)" % (f.__module__, f.__name__),
|
|
90
|
+
]
|
|
91
|
+
representations[types.FunctionType] = function
|
|
92
|
+
_list = [
|
|
93
|
+
lambda l: str(type(l)),
|
|
94
|
+
lambda l: "list, len=%s" % len(l)
|
|
95
|
+
]
|
|
96
|
+
representations[list] = _list
|
|
97
|
+
module = [lambda m: "module(%s)" % getattr(
|
|
98
|
+
m, '__name__', getattr(m, '__file__', 'nameless, id: %d' % id(m))
|
|
99
|
+
)]
|
|
100
|
+
representations[types.ModuleType] = module
|
|
101
|
+
_set = [
|
|
102
|
+
lambda s: str(type(s)),
|
|
103
|
+
lambda s: "set, len=%s" % len(s)
|
|
104
|
+
]
|
|
105
|
+
representations[set] = _set
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
_init_representations()
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def summarize(objects):
|
|
112
|
+
"""Summarize an objects list.
|
|
113
|
+
|
|
114
|
+
Return a list of lists, whereas each row consists of::
|
|
115
|
+
[str(type), number of objects of this type, total size of these objects].
|
|
116
|
+
|
|
117
|
+
No guarantee regarding the order is given.
|
|
118
|
+
|
|
119
|
+
"""
|
|
120
|
+
count = {}
|
|
121
|
+
total_size = {}
|
|
122
|
+
for o in objects:
|
|
123
|
+
otype = _repr(o)
|
|
124
|
+
if otype in count:
|
|
125
|
+
count[otype] += 1
|
|
126
|
+
total_size[otype] += getsizeof(o)
|
|
127
|
+
else:
|
|
128
|
+
count[otype] = 1
|
|
129
|
+
total_size[otype] = getsizeof(o)
|
|
130
|
+
rows = []
|
|
131
|
+
for otype in count:
|
|
132
|
+
rows.append([otype, count[otype], total_size[otype]])
|
|
133
|
+
return rows
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def get_diff(left, right):
|
|
137
|
+
"""Get the difference of two summaries.
|
|
138
|
+
|
|
139
|
+
Subtracts the values of the right summary from the values of the left
|
|
140
|
+
summary.
|
|
141
|
+
If similar rows appear on both sides, the are included in the summary with
|
|
142
|
+
0 for number of elements and total size.
|
|
143
|
+
If the number of elements of a row of the diff is 0, but the total size is
|
|
144
|
+
not, it means that objects likely have changed, but not there number, thus
|
|
145
|
+
resulting in a changed size.
|
|
146
|
+
|
|
147
|
+
"""
|
|
148
|
+
res = []
|
|
149
|
+
|
|
150
|
+
right_by_key = dict((r[0], r) for r in right)
|
|
151
|
+
left_by_key = dict((r[0], r) for r in left)
|
|
152
|
+
|
|
153
|
+
keys = set(right_by_key)
|
|
154
|
+
keys.update(left_by_key)
|
|
155
|
+
|
|
156
|
+
for key in keys:
|
|
157
|
+
r = right_by_key.get(key)
|
|
158
|
+
l = left_by_key.get(key)
|
|
159
|
+
if l and r:
|
|
160
|
+
res.append([key, r[1] - l[1], r[2] - l[2]])
|
|
161
|
+
elif r:
|
|
162
|
+
res.append(r)
|
|
163
|
+
elif l:
|
|
164
|
+
res.append([key, -l[1], -l[2]])
|
|
165
|
+
else:
|
|
166
|
+
continue # shouldn't happen
|
|
167
|
+
return res
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def format_(rows, limit=15, sort='size', order='descending'):
|
|
171
|
+
"""Format the rows as a summary.
|
|
172
|
+
|
|
173
|
+
Keyword arguments:
|
|
174
|
+
limit -- the maximum number of elements to be listed
|
|
175
|
+
sort -- sort elements by 'size', 'type', or '#'
|
|
176
|
+
order -- sort 'ascending' or 'descending'
|
|
177
|
+
"""
|
|
178
|
+
localrows = []
|
|
179
|
+
for row in rows:
|
|
180
|
+
localrows.append(list(row))
|
|
181
|
+
# input validation
|
|
182
|
+
sortby = ['type', '#', 'size']
|
|
183
|
+
if sort not in sortby:
|
|
184
|
+
raise ValueError("invalid sort, should be one of" + str(sortby))
|
|
185
|
+
orders = ['ascending', 'descending']
|
|
186
|
+
if order not in orders:
|
|
187
|
+
raise ValueError("invalid order, should be one of" + str(orders))
|
|
188
|
+
# sort rows
|
|
189
|
+
if sortby.index(sort) == 0:
|
|
190
|
+
if order == "ascending":
|
|
191
|
+
localrows.sort(key=lambda x: _repr(x[0]))
|
|
192
|
+
elif order == "descending":
|
|
193
|
+
localrows.sort(key=lambda x: _repr(x[0]), reverse=True)
|
|
194
|
+
else:
|
|
195
|
+
if order == "ascending":
|
|
196
|
+
localrows.sort(key=lambda x: x[sortby.index(sort)])
|
|
197
|
+
elif order == "descending":
|
|
198
|
+
localrows.sort(key=lambda x: x[sortby.index(sort)], reverse=True)
|
|
199
|
+
# limit rows
|
|
200
|
+
localrows = localrows[0:limit]
|
|
201
|
+
for row in localrows:
|
|
202
|
+
row[2] = stringutils.pp(row[2])
|
|
203
|
+
# print rows
|
|
204
|
+
localrows.insert(0, ["types", "# objects", "total size"])
|
|
205
|
+
return _format_table(localrows)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _format_table(rows, header=True):
|
|
209
|
+
"""Format a list of lists as a pretty table.
|
|
210
|
+
|
|
211
|
+
Keyword arguments:
|
|
212
|
+
header -- if True the first row is treated as a table header
|
|
213
|
+
|
|
214
|
+
inspired by http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/267662
|
|
215
|
+
"""
|
|
216
|
+
border = "="
|
|
217
|
+
# vertical delimiter
|
|
218
|
+
vdelim = " | "
|
|
219
|
+
# padding nr. of spaces are left around the longest element in the
|
|
220
|
+
# column
|
|
221
|
+
padding = 1
|
|
222
|
+
# may be left,center,right
|
|
223
|
+
justify = 'right'
|
|
224
|
+
justify = {'left': str.ljust,
|
|
225
|
+
'center': str.center,
|
|
226
|
+
'right': str.rjust}[justify.lower()]
|
|
227
|
+
# calculate column widths (longest item in each col
|
|
228
|
+
# plus "padding" nr of spaces on both sides)
|
|
229
|
+
cols = zip(*rows)
|
|
230
|
+
colWidths = [max([len(str(item)) + 2 * padding for item in col])
|
|
231
|
+
for col in cols]
|
|
232
|
+
borderline = vdelim.join([w * border for w in colWidths])
|
|
233
|
+
for row in rows:
|
|
234
|
+
yield vdelim.join([justify(str(item), width)
|
|
235
|
+
for (item, width) in zip(row, colWidths)])
|
|
236
|
+
if header:
|
|
237
|
+
yield borderline
|
|
238
|
+
header = False
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def print_(rows, limit=15, sort='size', order='descending'):
|
|
242
|
+
"""Print the rows as a summary.
|
|
243
|
+
|
|
244
|
+
Keyword arguments:
|
|
245
|
+
limit -- the maximum number of elements to be listed
|
|
246
|
+
sort -- sort elements by 'size', 'type', or '#'
|
|
247
|
+
order -- sort 'ascending' or 'descending'
|
|
248
|
+
|
|
249
|
+
"""
|
|
250
|
+
for line in format_(rows, limit=limit, sort=sort, order=order):
|
|
251
|
+
print(line)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
# regular expressions used by _repr to replace default type representations
|
|
255
|
+
type_repr = re.compile(r"^<(type|class) '(\S+)'>$")
|
|
256
|
+
address = re.compile(r' at 0x[0-9a-f]+')
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _repr(o, verbosity=1):
|
|
260
|
+
"""Get meaning object representation.
|
|
261
|
+
|
|
262
|
+
This function should be used when the simple str(o) output would result in
|
|
263
|
+
too general data. E.g. "<type 'instance'" is less meaningful than
|
|
264
|
+
"instance: Foo".
|
|
265
|
+
|
|
266
|
+
Keyword arguments:
|
|
267
|
+
verbosity -- if True the first row is treated as a table header
|
|
268
|
+
|
|
269
|
+
"""
|
|
270
|
+
res = ""
|
|
271
|
+
|
|
272
|
+
t = type(o)
|
|
273
|
+
if (verbosity == 0) or (t not in representations):
|
|
274
|
+
res = str(t)
|
|
275
|
+
else:
|
|
276
|
+
verbosity -= 1
|
|
277
|
+
if len(representations[t]) <= verbosity:
|
|
278
|
+
verbosity = len(representations[t]) - 1
|
|
279
|
+
res = representations[t][verbosity](o)
|
|
280
|
+
|
|
281
|
+
res = address.sub('', res)
|
|
282
|
+
res = type_repr.sub(r'\2', res)
|
|
283
|
+
|
|
284
|
+
return res
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _traverse(summary, function, *args):
|
|
288
|
+
"""Traverse all objects of a summary and call function with each as a
|
|
289
|
+
parameter.
|
|
290
|
+
|
|
291
|
+
Using this function, the following objects will be traversed:
|
|
292
|
+
- the summary
|
|
293
|
+
- each row
|
|
294
|
+
- each item of a row
|
|
295
|
+
"""
|
|
296
|
+
function(summary, *args)
|
|
297
|
+
for row in summary:
|
|
298
|
+
function(row, *args)
|
|
299
|
+
for item in row:
|
|
300
|
+
function(item, *args)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def _subtract(summary, o):
|
|
304
|
+
"""Remove object o from the summary by subtracting it's size."""
|
|
305
|
+
found = False
|
|
306
|
+
row = [_repr(o), 1, getsizeof(o)]
|
|
307
|
+
for r in summary:
|
|
308
|
+
if r[0] == row[0]:
|
|
309
|
+
(r[1], r[2]) = (r[1] - row[1], r[2] - row[2])
|
|
310
|
+
found = True
|
|
311
|
+
if not found:
|
|
312
|
+
summary.append([row[0], -row[1], -row[2]])
|
|
313
|
+
return summary
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def _sweep(summary):
|
|
317
|
+
"""Remove all rows in which the total size and the total number of
|
|
318
|
+
objects is zero.
|
|
319
|
+
|
|
320
|
+
"""
|
|
321
|
+
return [row for row in summary if ((row[2] != 0) or (row[1] != 0))]
|