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,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()
|