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,238 @@
1
+ """
2
+ This module queries process memory allocation metrics from the operating
3
+ system. It provides a platform independent layer to get the amount of virtual
4
+ and physical memory allocated to the Python process.
5
+
6
+ Different mechanisms are implemented: Either the process stat file is read
7
+ (Linux), the `ps` command is executed (BSD/OSX/Solaris) or the resource module
8
+ is queried (Unix fallback). On Windows try to use the win32 module if
9
+ available. If all fails, return 0 for each attribute.
10
+
11
+ Windows without the win32 module is not supported.
12
+
13
+ >>> from pympler.process import ProcessMemoryInfo
14
+ >>> pmi = ProcessMemoryInfo()
15
+ >>> print ("Virtual size [Byte]: " + str(pmi.vsz)) # doctest: +ELLIPSIS
16
+ Virtual size [Byte]: ...
17
+ """
18
+
19
+ from typing import Iterable, List, Tuple
20
+
21
+ import logging
22
+ import threading
23
+
24
+ from mmap import PAGESIZE # type: ignore
25
+ from os import getpid
26
+ from subprocess import Popen, PIPE
27
+
28
+ from .util.stringutils import pp
29
+
30
+
31
+ class _ProcessMemoryInfo(object):
32
+ """Stores information about various process-level memory metrics. The
33
+ virtual size is stored in attribute `vsz`, the physical memory allocated to
34
+ the process in `rss`, and the number of (major) pagefaults in `pagefaults`.
35
+ On Linux, `data_segment`, `code_segment`, `shared_segment` and
36
+ `stack_segment` contain the number of Bytes allocated for the respective
37
+ segments. This is an abstract base class which needs to be overridden by
38
+ operating system specific implementations. This is done when importing the
39
+ module.
40
+ """
41
+
42
+ pagesize = PAGESIZE
43
+
44
+ def __init__(self) -> None:
45
+ self.pid = getpid()
46
+ self.rss = 0
47
+ self.vsz = 0
48
+ self.pagefaults = 0
49
+ self.os_specific = [] # type: List[Tuple[str, str]]
50
+
51
+ self.data_segment = 0
52
+ self.code_segment = 0
53
+ self.shared_segment = 0
54
+ self.stack_segment = 0
55
+
56
+ self.available = self.update()
57
+
58
+ def __repr__(self) -> str:
59
+ return "<%s vsz=%d rss=%d>" % (self.__class__.__name__,
60
+ self.vsz, self.rss)
61
+
62
+ def update(self) -> bool:
63
+ """
64
+ Refresh the information using platform instruments. Returns true if
65
+ this operation yields useful values on the current platform.
66
+ """
67
+ return False # pragma: no cover
68
+
69
+ def __sub__(self, other: '_ProcessMemoryInfo') -> Iterable[Tuple[str, int]]:
70
+ diff = [('Resident set size (delta)', self.rss - other.rss),
71
+ ('Virtual size (delta)', self.vsz - other.vsz),
72
+ ]
73
+ return diff
74
+
75
+
76
+ ProcessMemoryInfo = _ProcessMemoryInfo # type: type
77
+
78
+
79
+ def is_available() -> bool:
80
+ """
81
+ Convenience function to check if the current platform is supported by this
82
+ module.
83
+ """
84
+ return ProcessMemoryInfo().update()
85
+
86
+
87
+ class _ProcessMemoryInfoPS(_ProcessMemoryInfo):
88
+
89
+ def update(self) -> bool:
90
+ """
91
+ Get virtual and resident size of current process via 'ps'.
92
+ This should work for MacOS X, Solaris, Linux. Returns true if it was
93
+ successful.
94
+ """
95
+ try:
96
+ p = Popen(['/bin/ps', '-p%s' % self.pid, '-o', 'rss,vsz'],
97
+ stdout=PIPE, stderr=PIPE)
98
+ except OSError: # pragma: no cover
99
+ pass
100
+ else:
101
+ s = p.communicate()[0].split()
102
+ if p.returncode == 0 and len(s) >= 2: # pragma: no branch
103
+ self.vsz = int(s[-1]) * 1024
104
+ self.rss = int(s[-2]) * 1024
105
+ return True
106
+ return False # pragma: no cover
107
+
108
+
109
+ class _ProcessMemoryInfoProc(_ProcessMemoryInfo):
110
+
111
+ key_map = {
112
+ 'VmPeak': 'Peak virtual memory size',
113
+ 'VmSize': 'Virtual memory size',
114
+ 'VmLck': 'Locked memory size',
115
+ 'VmHWM': 'Peak resident set size',
116
+ 'VmRSS': 'Resident set size',
117
+ 'VmStk': 'Size of stack segment',
118
+ 'VmData': 'Size of data segment',
119
+ 'VmExe': 'Size of code segment',
120
+ 'VmLib': 'Shared library code size',
121
+ 'VmPTE': 'Page table entries size',
122
+ }
123
+
124
+ def update(self) -> bool:
125
+ """
126
+ Get virtual size of current process by reading the process' stat file.
127
+ This should work for Linux.
128
+ """
129
+ try:
130
+ stat = open('/proc/self/stat')
131
+ status = open('/proc/self/status')
132
+ except IOError: # pragma: no cover
133
+ return False
134
+ else:
135
+ stats = stat.read().split()
136
+ self.vsz = int(stats[22])
137
+ self.rss = int(stats[23]) * self.pagesize
138
+ self.pagefaults = int(stats[11])
139
+
140
+ for entry in status.readlines():
141
+ try:
142
+ key, value = entry.split(':', 1)
143
+ except ValueError:
144
+ continue
145
+ value = value.strip()
146
+
147
+ def size_in_bytes(x: str) -> int:
148
+ return int(x.split()[0]) * 1024
149
+
150
+ if key == 'VmData':
151
+ self.data_segment = size_in_bytes(value)
152
+ elif key == 'VmExe':
153
+ self.code_segment = size_in_bytes(value)
154
+ elif key == 'VmLib':
155
+ self.shared_segment = size_in_bytes(value)
156
+ elif key == 'VmStk':
157
+ self.stack_segment = size_in_bytes(value)
158
+ key = self.key_map.get(key, '')
159
+ if key:
160
+ self.os_specific.append((key, pp(size_in_bytes(value))))
161
+
162
+ stat.close()
163
+ status.close()
164
+ return True
165
+
166
+
167
+ try:
168
+ from resource import getrusage, RUSAGE_SELF # type: ignore
169
+
170
+ class _ProcessMemoryInfoResource(_ProcessMemoryInfo):
171
+ def update(self) -> bool:
172
+ """
173
+ Get memory metrics of current process through `getrusage`. Only
174
+ available on Unix, on Linux most of the fields are not set,
175
+ and on BSD units are used that are not very helpful, see:
176
+
177
+ http://www.perlmonks.org/?node_id=626693
178
+
179
+ Furthermore, getrusage only provides accumulated statistics (e.g.
180
+ max rss vs current rss).
181
+ """
182
+ usage = getrusage(RUSAGE_SELF)
183
+ self.rss = usage.ru_maxrss * 1024
184
+ self.data_segment = usage.ru_idrss * 1024 # TODO: ticks?
185
+ self.shared_segment = usage.ru_ixrss * 1024 # TODO: ticks?
186
+ self.stack_segment = usage.ru_isrss * 1024 # TODO: ticks?
187
+ self.vsz = (self.data_segment + self.shared_segment +
188
+ self.stack_segment)
189
+
190
+ self.pagefaults = usage.ru_majflt
191
+ return self.rss != 0
192
+
193
+ if _ProcessMemoryInfoProc().update(): # pragma: no branch
194
+ ProcessMemoryInfo = _ProcessMemoryInfoProc
195
+ elif _ProcessMemoryInfoPS().update(): # pragma: no cover
196
+ ProcessMemoryInfo = _ProcessMemoryInfoPS
197
+ elif _ProcessMemoryInfoResource().update(): # pragma: no cover
198
+ ProcessMemoryInfo = _ProcessMemoryInfoResource
199
+
200
+ except ImportError:
201
+ try:
202
+ # Requires pywin32
203
+ from win32process import GetProcessMemoryInfo
204
+ from win32api import GetCurrentProcess, GlobalMemoryStatusEx
205
+ except ImportError:
206
+ logging.warn("Please install pywin32 when using pympler on Windows.")
207
+ else:
208
+ class _ProcessMemoryInfoWin32(_ProcessMemoryInfo):
209
+ def update(self) -> bool:
210
+ process_handle = GetCurrentProcess()
211
+ meminfo = GetProcessMemoryInfo(process_handle)
212
+ memstatus = GlobalMemoryStatusEx()
213
+ self.vsz = (memstatus['TotalVirtual'] -
214
+ memstatus['AvailVirtual'])
215
+ self.rss = meminfo['WorkingSetSize']
216
+ self.pagefaults = meminfo['PageFaultCount']
217
+ return True
218
+
219
+ ProcessMemoryInfo = _ProcessMemoryInfoWin32
220
+
221
+
222
+ class ThreadInfo(object):
223
+ """Collect information about an active thread."""
224
+
225
+ def __init__(self, thread: threading.Thread):
226
+ self.ident = thread.ident
227
+ self.name = thread.name
228
+ self.daemon = thread.daemon
229
+
230
+
231
+ def get_current_threads() -> Iterable[ThreadInfo]:
232
+ """Get a list of `ThreadInfo` objects."""
233
+ return [ThreadInfo(thread) for thread in threading.enumerate()]
234
+
235
+
236
+ def get_current_thread_id() -> int:
237
+ """Get the ID of the current thread."""
238
+ return threading.get_ident()
File without changes
@@ -0,0 +1,451 @@
1
+ """Tree-like exploration of object referrers.
2
+
3
+ This module provides a base implementation for tree-like referrers browsing.
4
+ The two non-interactive classes ConsoleBrowser and FileBrowser output a tree
5
+ to the console or a file. One graphical user interface for referrers browsing
6
+ is provided as well. Further types can be subclassed.
7
+
8
+ All types share a similar initialisation. That is, you provide a root object
9
+ and may specify further settings such as the initial depth of the tree or an
10
+ output function.
11
+ Afterwards you can print the tree which will be arranged based on your previous
12
+ settings.
13
+
14
+ The interactive browser is based on a TreeWidget implemented in IDLE. It is
15
+ available only if you have Tcl/Tk installed. If you try to instantiate the
16
+ interactive browser without having Tkinter installed, an ImportError will be
17
+ raised.
18
+
19
+ """
20
+ import gc
21
+ import inspect
22
+ import sys
23
+
24
+ from . import muppy
25
+ from . import summary
26
+
27
+ from .util.compat import tkinter
28
+
29
+
30
+ class _Node(object):
31
+ """A node as it is used in the tree structure.
32
+
33
+ Each node contains the object it represents and a list of children.
34
+ Children can be other nodes or arbitrary other objects. Any object
35
+ in a tree which is not of the type _Node is considered a leaf.
36
+
37
+ """
38
+ def __init__(self, o, str_func=None):
39
+ """You have to define the object this node represents. Also you can
40
+ define an output function which will be used to represent this node.
41
+ If no function is defined, the default str representation is used.
42
+
43
+ keyword arguments
44
+ str_func -- output function
45
+
46
+ """
47
+ self.o = o
48
+ self.children = []
49
+ self.str_func = str_func
50
+
51
+ def __str__(self):
52
+ """Override str(self.o) if str_func is defined."""
53
+ if self.str_func is not None:
54
+ return self.str_func(self.o)
55
+ else:
56
+ return str(self.o)
57
+
58
+
59
+ class RefBrowser(object):
60
+ """Base class to other RefBrowser implementations.
61
+
62
+ This base class provides means to extract a tree from a given root object
63
+ and holds information on already known objects (to avoid repetition
64
+ if requested).
65
+
66
+ """
67
+
68
+ def __init__(self, rootobject, maxdepth=3, str_func=summary._repr,
69
+ repeat=True, stream=None):
70
+ """You have to provide the root object used in the refbrowser.
71
+
72
+ keyword arguments
73
+ maxdepth -- maximum depth of the initial tree
74
+ str_func -- function used when calling str(node)
75
+ repeat -- should nodes appear repeatedly in the tree, or should be
76
+ referred to existing nodes
77
+ stream -- output stream (used in derived classes)
78
+
79
+ """
80
+ self.root = rootobject
81
+ self.maxdepth = maxdepth
82
+ self.str_func = str_func
83
+ self.repeat = repeat
84
+ self.stream = stream
85
+ # objects which should be ignored while building the tree
86
+ # e.g. the current frame
87
+ self.ignore = []
88
+ # set of object ids which are already included
89
+ self.already_included = set()
90
+ self.ignore.append(self.already_included)
91
+
92
+ def get_tree(self):
93
+ """Get a tree of referrers of the root object."""
94
+ self.ignore.append(inspect.currentframe())
95
+ return self._get_tree(self.root, self.maxdepth)
96
+
97
+ def _get_tree(self, root, maxdepth):
98
+ """Workhorse of the get_tree implementation.
99
+
100
+ This is a recursive method which is why we have a wrapper method.
101
+ root is the current root object of the tree which should be returned.
102
+ Note that root is not of the type _Node.
103
+ maxdepth defines how much further down the from the root the tree
104
+ should be build.
105
+
106
+ """
107
+ objects = gc.get_referrers(root)
108
+ res = _Node(root, self.str_func)
109
+ self.already_included.add(id(root))
110
+ if maxdepth == 0:
111
+ return res
112
+ self.ignore.append(inspect.currentframe())
113
+ self.ignore.append(objects)
114
+ for o in objects:
115
+ # Ignore dict of _Node and RefBrowser objects
116
+ if isinstance(o, dict) and sys.version_info < (3, 11):
117
+ if any(isinstance(ref, (_Node, RefBrowser))
118
+ for ref in gc.get_referrers(o)):
119
+ continue
120
+ _id = id(o)
121
+ if not self.repeat and (_id in self.already_included):
122
+ s = self.str_func(o)
123
+ res.children.append("%s (already included, id %s)" %
124
+ (s, _id))
125
+ continue
126
+ if (not isinstance(o, _Node)) and (o not in self.ignore):
127
+ res.children.append(self._get_tree(o, maxdepth - 1))
128
+ return res
129
+
130
+
131
+ class StreamBrowser(RefBrowser):
132
+ """RefBrowser implementation which prints the tree to the console.
133
+
134
+ If you don't like the looks, you can change it a little bit.
135
+ The class attributes 'hline', 'vline', 'cross', and 'space' can be
136
+ modified to your needs.
137
+
138
+ """
139
+ hline = '-'
140
+ vline = '|'
141
+ cross = '+'
142
+ space = ' '
143
+
144
+ def print_tree(self, tree=None):
145
+ """ Print referrers tree to console.
146
+
147
+ keyword arguments
148
+ tree -- if not None, the passed tree will be printed. Otherwise it is
149
+ based on the rootobject.
150
+
151
+ """
152
+ if tree is None:
153
+ tree = self.get_tree()
154
+ self._print(tree, '', '')
155
+
156
+ def _print(self, tree, prefix, carryon):
157
+ """Compute and print a new line of the tree.
158
+
159
+ This is a recursive function.
160
+
161
+ arguments
162
+ tree -- tree to print
163
+ prefix -- prefix to the current line to print
164
+ carryon -- prefix which is used to carry on the vertical lines
165
+
166
+ """
167
+ level = prefix.count(self.cross) + prefix.count(self.vline)
168
+ len_children = 0
169
+ if isinstance(tree, _Node):
170
+ len_children = len(tree.children)
171
+
172
+ # add vertex
173
+ prefix += str(tree)
174
+ # and as many spaces as the vertex is long
175
+ carryon += self.space * len(str(tree))
176
+ if (level == self.maxdepth) or (not isinstance(tree, _Node)) or\
177
+ (len_children == 0):
178
+ self.stream.write(prefix + '\n')
179
+ return
180
+ else:
181
+ # add in between connections
182
+ prefix += self.hline
183
+ carryon += self.space
184
+ # if there is more than one branch, add a cross
185
+ if len(tree.children) > 1:
186
+ prefix += self.cross
187
+ carryon += self.vline
188
+ prefix += self.hline
189
+ carryon += self.space
190
+
191
+ if len_children > 0:
192
+ # print the first branch (on the same line)
193
+ self._print(tree.children[0], prefix, carryon)
194
+ for b in range(1, len_children):
195
+ # the carryon becomes the prefix for all following children
196
+ prefix = carryon[:-2] + self.cross + self.hline
197
+ # remove the vlines for any children of last branch
198
+ if b == (len_children - 1):
199
+ carryon = carryon[:-2] + 2 * self.space
200
+ self._print(tree.children[b], prefix, carryon)
201
+ # leave a free line before the next branch
202
+ if b == (len_children - 1):
203
+ if len(carryon.strip(' ')) == 0:
204
+ return
205
+ self.stream.write(carryon[:-2].rstrip() + '\n')
206
+
207
+
208
+ class ConsoleBrowser(StreamBrowser):
209
+ """RefBrowser that prints to the console (stdout)."""
210
+
211
+ def __init__(self, *args, **kwargs):
212
+ super(ConsoleBrowser, self).__init__(*args, **kwargs)
213
+ if not self.stream:
214
+ self.stream = sys.stdout
215
+
216
+
217
+ class FileBrowser(StreamBrowser):
218
+ """RefBrowser implementation which prints the tree to a file."""
219
+
220
+ def print_tree(self, filename, tree=None):
221
+ """ Print referrers tree to file (in text format).
222
+
223
+ keyword arguments
224
+ tree -- if not None, the passed tree will be printed.
225
+
226
+ """
227
+ old_stream = self.stream
228
+ self.stream = open(filename, 'w')
229
+ try:
230
+ super(FileBrowser, self).print_tree(tree=tree)
231
+ finally:
232
+ self.stream.close()
233
+ self.stream = old_stream
234
+
235
+
236
+ # Code for interactive browser (GUI)
237
+ # ==================================
238
+
239
+ # The interactive browser requires Tkinter which is not always available. To
240
+ # avoid an import error when loading the module, we encapsulate most of the
241
+ # code in the following try-except-block. The InteractiveBrowser itself
242
+ # remains outside this block. If you try to instantiate it without having
243
+ # Tkinter installed, the import error will be raised.
244
+ try:
245
+ if sys.version_info < (3, 5, 2):
246
+ from idlelib import TreeWidget as _TreeWidget
247
+ else:
248
+ from idlelib import tree as _TreeWidget
249
+
250
+ class _TreeNode(_TreeWidget.TreeNode):
251
+ """TreeNode used by the InteractiveBrowser.
252
+
253
+ Not to be confused with _Node. This one is used in the GUI
254
+ context.
255
+
256
+ """
257
+ def reload_referrers(self):
258
+ """Reload all referrers for this _TreeNode."""
259
+ self.item.node = self.item.reftree._get_tree(self.item.node.o, 1)
260
+ self.item._clear_children()
261
+ self.expand()
262
+ self.update()
263
+
264
+ def print_object(self):
265
+ """Print object which this _TreeNode represents to console."""
266
+ print(self.item.node.o)
267
+
268
+ def drawtext(self):
269
+ """Override drawtext from _TreeWidget.TreeNode.
270
+
271
+ This seems to be a good place to add the popup menu.
272
+
273
+ """
274
+ _TreeWidget.TreeNode.drawtext(self)
275
+ # create a menu
276
+ menu = tkinter.Menu(self.canvas, tearoff=0)
277
+ menu.add_command(label="reload referrers",
278
+ command=self.reload_referrers)
279
+ menu.add_command(label="print", command=self.print_object)
280
+ menu.add_separator()
281
+ menu.add_command(label="expand", command=self.expand)
282
+ menu.add_separator()
283
+ # the popup only disappears when to click on it
284
+ menu.add_command(label="Close Popup Menu")
285
+
286
+ def do_popup(event):
287
+ menu.post(event.x_root, event.y_root)
288
+
289
+ self.label.bind("<Button-3>", do_popup)
290
+ # override, i.e. disable the editing of items
291
+
292
+ # disable editing of TreeNodes
293
+ def edit(self, event=None):
294
+ pass # see comment above
295
+
296
+ def edit_finish(self, event=None):
297
+ pass # see comment above
298
+
299
+ def edit_cancel(self, event=None):
300
+ pass # see comment above
301
+
302
+ class _ReferrerTreeItem(_TreeWidget.TreeItem, tkinter.Label):
303
+ """Tree item wrapper around _Node object."""
304
+
305
+ def __init__(self, parentwindow, node, reftree): # constr calls
306
+ """You need to provide the parent window, the node this TreeItem
307
+ represents, as well as the tree (_Node) which the node
308
+ belongs to.
309
+
310
+ """
311
+ _TreeWidget.TreeItem.__init__(self)
312
+ tkinter.Label.__init__(self, parentwindow)
313
+ self.node = node
314
+ self.parentwindow = parentwindow
315
+ self.reftree = reftree
316
+
317
+ def _clear_children(self):
318
+ """Clear children list from any TreeNode instances.
319
+
320
+ Normally these objects are not required for memory profiling, as
321
+ they are part of the profiler.
322
+
323
+ """
324
+ new_children = []
325
+ for child in self.node.children:
326
+ if not isinstance(child, _TreeNode):
327
+ new_children.append(child)
328
+ self.node.children = new_children
329
+
330
+ def GetText(self):
331
+ return str(self.node)
332
+
333
+ def GetIconName(self):
334
+ """Different icon when object cannot be expanded, i.e. has no
335
+ referrers.
336
+
337
+ """
338
+ if not self.IsExpandable():
339
+ return "python"
340
+
341
+ def IsExpandable(self):
342
+ """An object is expandable when it is a node which has children and
343
+ is a container object.
344
+
345
+ """
346
+ if not isinstance(self.node, _Node):
347
+ return False
348
+ else:
349
+ if len(self.node.children) > 0:
350
+ return True
351
+ else:
352
+ return muppy._is_containerobject(self.node.o)
353
+
354
+ def GetSubList(self):
355
+ """This method is the point where further referrers are computed.
356
+
357
+ Thus, the computation is done on-demand and only when needed.
358
+
359
+ """
360
+ sublist = []
361
+
362
+ children = self.node.children
363
+ if (len(children) == 0) and\
364
+ (muppy._is_containerobject(self.node.o)):
365
+ self.node = self.reftree._get_tree(self.node.o, 1)
366
+ self._clear_children()
367
+ children = self.node.children
368
+
369
+ for child in children:
370
+ item = _ReferrerTreeItem(self.parentwindow, child,
371
+ self.reftree)
372
+ sublist.append(item)
373
+ return sublist
374
+
375
+ except ImportError:
376
+ _TreeWidget = None
377
+
378
+
379
+ def gui_default_str_function(o):
380
+ """Default str function for InteractiveBrowser."""
381
+ return summary._repr(o) + '(id=%s)' % id(o)
382
+
383
+
384
+ class InteractiveBrowser(RefBrowser):
385
+ """Interactive referrers browser.
386
+
387
+ The interactive browser is based on a TreeWidget implemented in IDLE. It is
388
+ available only if you have Tcl/Tk installed. If you try to instantiate the
389
+ interactive browser without having Tkinter installed, an ImportError will
390
+ be raised.
391
+
392
+ """
393
+ def __init__(self, rootobject, maxdepth=3,
394
+ str_func=gui_default_str_function, repeat=True):
395
+ """You have to provide the root object used in the refbrowser.
396
+
397
+ keyword arguments
398
+ maxdepth -- maximum depth of the initial tree
399
+ str_func -- function used when calling str(node)
400
+ repeat -- should nodes appear repeatedly in the tree, or should be
401
+ referred to existing nodes
402
+
403
+ """
404
+ if tkinter is None:
405
+ raise ImportError(
406
+ "InteractiveBrowser requires Tkinter to be installed.")
407
+ RefBrowser.__init__(self, rootobject, maxdepth, str_func, repeat)
408
+
409
+ def main(self, standalone=False):
410
+ """Create interactive browser window.
411
+
412
+ keyword arguments
413
+ standalone -- Set to true, if the browser is not attached to other
414
+ windows
415
+
416
+ """
417
+ window = tkinter.Tk()
418
+ sc = _TreeWidget.ScrolledCanvas(window, bg="white",
419
+ highlightthickness=0, takefocus=1)
420
+ sc.frame.pack(expand=1, fill="both")
421
+ item = _ReferrerTreeItem(window, self.get_tree(), self)
422
+ node = _TreeNode(sc.canvas, None, item)
423
+ node.expand()
424
+ if standalone:
425
+ window.mainloop()
426
+
427
+
428
+ # list to hold to referrers
429
+ superlist = []
430
+ root = "root"
431
+ for i in range(3):
432
+ tmp = [root]
433
+ superlist.append(tmp)
434
+
435
+
436
+ def foo(o):
437
+ return str(type(o))
438
+
439
+
440
+ def print_sample():
441
+ cb = ConsoleBrowser(root, str_func=foo)
442
+ cb.print_tree()
443
+
444
+
445
+ def write_sample():
446
+ fb = FileBrowser(root, str_func=foo)
447
+ fb.print_tree('sample.txt')
448
+
449
+
450
+ if __name__ == "__main__":
451
+ write_sample()