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.
Files changed (180) hide show
  1. cavisson_pythonagent-0.0.1.dist-info/METADATA +32 -0
  2. cavisson_pythonagent-0.0.1.dist-info/RECORD +180 -0
  3. cavisson_pythonagent-0.0.1.dist-info/WHEEL +5 -0
  4. cavisson_pythonagent-0.0.1.dist-info/licenses/LICENSE +19 -0
  5. cavisson_pythonagent-0.0.1.dist-info/top_level.txt +1 -0
  6. pythonagent/__init__.py +22 -0
  7. pythonagent/agent/__init__.py +219 -0
  8. pythonagent/agent/internal/__init__.py +1 -0
  9. pythonagent/agent/internal/agent.py +1651 -0
  10. pythonagent/agent/internal/framesinfo.py +152 -0
  11. pythonagent/agent/internal/heap_dump.py +39 -0
  12. pythonagent/agent/internal/intercept_module.py +70 -0
  13. pythonagent/agent/internal/logs.py +122 -0
  14. pythonagent/agent/internal/metadata/__init__.py +0 -0
  15. pythonagent/agent/internal/metadata/agent_meta_data.py +276 -0
  16. pythonagent/agent/internal/proc_compat.py +75 -0
  17. pythonagent/agent/internal/profile.py +47 -0
  18. pythonagent/agent/internal/provider.py +83 -0
  19. pythonagent/agent/internal/thread_dump.py +85 -0
  20. pythonagent/agent/internal/udp.py +245 -0
  21. pythonagent/agent/internal/udp_message.py +800 -0
  22. pythonagent/agent/probes/Instrumentation/__init__.py +340 -0
  23. pythonagent/agent/probes/Instrumentation/find.py +243 -0
  24. pythonagent/agent/probes/Instrumentation/module_version_resolver.py +155 -0
  25. pythonagent/agent/probes/Instrumentation/new_parser.py +64 -0
  26. pythonagent/agent/probes/Instrumentation/parser.py +129 -0
  27. pythonagent/agent/probes/__init__.py +219 -0
  28. pythonagent/agent/probes/base.py +303 -0
  29. pythonagent/agent/probes/cache/__init__.py +51 -0
  30. pythonagent/agent/probes/cache/redis.py +119 -0
  31. pythonagent/agent/probes/cache/redis_asyncio.py +83 -0
  32. pythonagent/agent/probes/coroutines/__init__.py +1 -0
  33. pythonagent/agent/probes/coroutines/asyncio.py +63 -0
  34. pythonagent/agent/probes/elasticdb/__init__.py +7 -0
  35. pythonagent/agent/probes/elasticdb/aelastic.py +54 -0
  36. pythonagent/agent/probes/frameworks/__init__.py +27 -0
  37. pythonagent/agent/probes/frameworks/agentprofiler.py +105 -0
  38. pythonagent/agent/probes/frameworks/aiohttp_web.py +155 -0
  39. pythonagent/agent/probes/frameworks/aisess.py +151 -0
  40. pythonagent/agent/probes/frameworks/asgi.py +340 -0
  41. pythonagent/agent/probes/frameworks/bottle.py +27 -0
  42. pythonagent/agent/probes/frameworks/cherry.py +25 -0
  43. pythonagent/agent/probes/frameworks/django.py +128 -0
  44. pythonagent/agent/probes/frameworks/falcon.py +21 -0
  45. pythonagent/agent/probes/frameworks/fastapi.py +35 -0
  46. pythonagent/agent/probes/frameworks/flask.py +30 -0
  47. pythonagent/agent/probes/frameworks/pyramid.py +56 -0
  48. pythonagent/agent/probes/frameworks/test.py +108 -0
  49. pythonagent/agent/probes/frameworks/tornado_async_web.py +117 -0
  50. pythonagent/agent/probes/frameworks/tornado_web.py +133 -0
  51. pythonagent/agent/probes/frameworks/wsgi.py +353 -0
  52. pythonagent/agent/probes/grpc/__init__.py +76 -0
  53. pythonagent/agent/probes/grpc/client_interceptor.py +132 -0
  54. pythonagent/agent/probes/grpc/server_interceptor.py +129 -0
  55. pythonagent/agent/probes/havoc/__init__.py +0 -0
  56. pythonagent/agent/probes/havoc/custom_memory_stress.py +186 -0
  57. pythonagent/agent/probes/havoc/custom_thread_stress.py +187 -0
  58. pythonagent/agent/probes/havoc/havoc_constants.py +218 -0
  59. pythonagent/agent/probes/havoc/havoc_manager.py +981 -0
  60. pythonagent/agent/probes/http/__init__.py +49 -0
  61. pythonagent/agent/probes/http/aiohttp_client.py +59 -0
  62. pythonagent/agent/probes/http/boto.py +12 -0
  63. pythonagent/agent/probes/http/httplib.py +110 -0
  64. pythonagent/agent/probes/http/httpx_client.py +116 -0
  65. pythonagent/agent/probes/http/requests.py +15 -0
  66. pythonagent/agent/probes/http/tornado_httpclient.py +85 -0
  67. pythonagent/agent/probes/http/urllib3.py +16 -0
  68. pythonagent/agent/probes/langchain/__init__.py +21 -0
  69. pythonagent/agent/probes/langchain/base_tool.py +95 -0
  70. pythonagent/agent/probes/langchain/langchain_community.py +136 -0
  71. pythonagent/agent/probes/langchain/langchain_core.py +32 -0
  72. pythonagent/agent/probes/langchain/langchain_openai.py +110 -0
  73. pythonagent/agent/probes/logging/__init__.py +106 -0
  74. pythonagent/agent/probes/message_brokers/__init__.py +4 -0
  75. pythonagent/agent/probes/message_brokers/pika.py +126 -0
  76. pythonagent/agent/probes/mongodb/__init__.py +6 -0
  77. pythonagent/agent/probes/mongodb/pymongo.py +286 -0
  78. pythonagent/agent/probes/openai/__init__.py +3 -0
  79. pythonagent/agent/probes/openai/openai.py +797 -0
  80. pythonagent/agent/probes/span.py +101 -0
  81. pythonagent/agent/probes/sql/__init__.py +13 -0
  82. pythonagent/agent/probes/sql/botocores3.py +51 -0
  83. pythonagent/agent/probes/sql/dbapi.py +285 -0
  84. pythonagent/agent/probes/sql/dynamodb.py +90 -0
  85. pythonagent/agent/probes/sql/mysql_connector.py +24 -0
  86. pythonagent/agent/probes/sql/mysql_connector_cext.py +24 -0
  87. pythonagent/agent/probes/sql/mysqldb.py +43 -0
  88. pythonagent/agent/probes/sql/psycopg2.py +174 -0
  89. pythonagent/agent/probes/sql/pymysql.py +25 -0
  90. pythonagent/bootstrap/__init__.py +0 -0
  91. pythonagent/bootstrap/cav_gunicorn.py +26 -0
  92. pythonagent/bootstrap/cavagent_lambda_wrapper.py +291 -0
  93. pythonagent/bootstrap/run.py +47 -0
  94. pythonagent/bootstrap/sitecustomize.py +287 -0
  95. pythonagent/cavisson/netdiagnostics/CavAgent/instrumentationprofile.json +26 -0
  96. pythonagent/cavisson/netdiagnostics/CavAgent/interceptor_points.txt +29 -0
  97. pythonagent/cavisson/netdiagnostics/python/CavAgent/instrumentationprofile.json +42 -0
  98. pythonagent/cavisson/netdiagnostics/python/CavAgent/interceptor_points.txt +29 -0
  99. pythonagent/cavisson/netdiagnostics/python/config/ndsettings.conf +6 -0
  100. pythonagent/config.py +279 -0
  101. pythonagent/find.py +72 -0
  102. pythonagent/find_mod_cls_name.py +54 -0
  103. pythonagent/lang.py +131 -0
  104. pythonagent/lib.py +91 -0
  105. pythonagent/main/__init__.py +0 -0
  106. pythonagent/main/pytrace/__init__.py +79 -0
  107. pythonagent/main/pytrace/commands/__init__.py +0 -0
  108. pythonagent/main/pytrace/commands/auto_discovery.py +25 -0
  109. pythonagent/main/pytrace/commands/run.py +401 -0
  110. pythonagent/main/pytrace/pytrace.py +133 -0
  111. pythonagent/main/wsgi.py +6 -0
  112. pythonagent/main.py +27 -0
  113. pythonagent/run.py +46 -0
  114. pythonagent/sqins.py +8 -0
  115. pythonagent/test.py +73 -0
  116. pythonagent/utils.py +168 -0
  117. pythonagent/vendor/__init__.py +0 -0
  118. pythonagent/vendor/pympler/__init__.py +1 -0
  119. pythonagent/vendor/pympler/asizeof.py +2810 -0
  120. pythonagent/vendor/pympler/charts.py +62 -0
  121. pythonagent/vendor/pympler/classtracker.py +590 -0
  122. pythonagent/vendor/pympler/classtracker_stats.py +780 -0
  123. pythonagent/vendor/pympler/garbagegraph.py +80 -0
  124. pythonagent/vendor/pympler/mprofile.py +97 -0
  125. pythonagent/vendor/pympler/muppy.py +275 -0
  126. pythonagent/vendor/pympler/panels.py +115 -0
  127. pythonagent/vendor/pympler/process.py +238 -0
  128. pythonagent/vendor/pympler/py.typed +0 -0
  129. pythonagent/vendor/pympler/refbrowser.py +451 -0
  130. pythonagent/vendor/pympler/refgraph.py +350 -0
  131. pythonagent/vendor/pympler/summary.py +321 -0
  132. pythonagent/vendor/pympler/tracker.py +267 -0
  133. pythonagent/vendor/pympler/util/__init__.py +0 -0
  134. pythonagent/vendor/pympler/util/bottle.py +3809 -0
  135. pythonagent/vendor/pympler/util/compat.py +23 -0
  136. pythonagent/vendor/pympler/util/stringutils.py +77 -0
  137. pythonagent/vendor/pympler/web.py +346 -0
  138. pythonagent/vendor/werkzeug/__init__.py +20 -0
  139. pythonagent/vendor/werkzeug/_compat.py +228 -0
  140. pythonagent/vendor/werkzeug/_internal.py +473 -0
  141. pythonagent/vendor/werkzeug/_reloader.py +341 -0
  142. pythonagent/vendor/werkzeug/datastructures.py +3120 -0
  143. pythonagent/vendor/werkzeug/debug/__init__.py +498 -0
  144. pythonagent/vendor/werkzeug/debug/console.py +218 -0
  145. pythonagent/vendor/werkzeug/debug/repr.py +297 -0
  146. pythonagent/vendor/werkzeug/debug/tbtools.py +628 -0
  147. pythonagent/vendor/werkzeug/exceptions.py +829 -0
  148. pythonagent/vendor/werkzeug/filesystem.py +64 -0
  149. pythonagent/vendor/werkzeug/formparser.py +584 -0
  150. pythonagent/vendor/werkzeug/http.py +1307 -0
  151. pythonagent/vendor/werkzeug/local.py +420 -0
  152. pythonagent/vendor/werkzeug/middleware/__init__.py +25 -0
  153. pythonagent/vendor/werkzeug/middleware/dispatcher.py +66 -0
  154. pythonagent/vendor/werkzeug/middleware/http_proxy.py +219 -0
  155. pythonagent/vendor/werkzeug/middleware/lint.py +408 -0
  156. pythonagent/vendor/werkzeug/middleware/profiler.py +132 -0
  157. pythonagent/vendor/werkzeug/middleware/proxy_fix.py +169 -0
  158. pythonagent/vendor/werkzeug/middleware/shared_data.py +293 -0
  159. pythonagent/vendor/werkzeug/posixemulation.py +117 -0
  160. pythonagent/vendor/werkzeug/routing.py +2210 -0
  161. pythonagent/vendor/werkzeug/security.py +249 -0
  162. pythonagent/vendor/werkzeug/serving.py +1117 -0
  163. pythonagent/vendor/werkzeug/test.py +1123 -0
  164. pythonagent/vendor/werkzeug/testapp.py +241 -0
  165. pythonagent/vendor/werkzeug/urls.py +1138 -0
  166. pythonagent/vendor/werkzeug/useragents.py +202 -0
  167. pythonagent/vendor/werkzeug/utils.py +778 -0
  168. pythonagent/vendor/werkzeug/wrappers/__init__.py +36 -0
  169. pythonagent/vendor/werkzeug/wrappers/accept.py +50 -0
  170. pythonagent/vendor/werkzeug/wrappers/auth.py +33 -0
  171. pythonagent/vendor/werkzeug/wrappers/base_request.py +673 -0
  172. pythonagent/vendor/werkzeug/wrappers/base_response.py +700 -0
  173. pythonagent/vendor/werkzeug/wrappers/common_descriptors.py +341 -0
  174. pythonagent/vendor/werkzeug/wrappers/cors.py +100 -0
  175. pythonagent/vendor/werkzeug/wrappers/etag.py +304 -0
  176. pythonagent/vendor/werkzeug/wrappers/json.py +145 -0
  177. pythonagent/vendor/werkzeug/wrappers/request.py +49 -0
  178. pythonagent/vendor/werkzeug/wrappers/response.py +84 -0
  179. pythonagent/vendor/werkzeug/wrappers/user_agent.py +14 -0
  180. 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))]