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,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Compatibility layer to allow Pympler being used from different Python versions.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any, Iterable
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
import tkinter
|
|
9
|
+
except ImportError:
|
|
10
|
+
tkinter = None # type: ignore
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# Helper functions
|
|
14
|
+
|
|
15
|
+
def object_in_list(obj: Any, l: Iterable) -> bool:
|
|
16
|
+
"""Returns True if object o is in list.
|
|
17
|
+
|
|
18
|
+
Required compatibility function to handle WeakSet objects.
|
|
19
|
+
"""
|
|
20
|
+
for o in l:
|
|
21
|
+
if o is obj:
|
|
22
|
+
return True
|
|
23
|
+
return False
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""
|
|
2
|
+
String utility functions.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any, Optional, Union
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def safe_repr(obj: Any, clip: Optional[int] = None) -> str:
|
|
9
|
+
"""
|
|
10
|
+
Convert object to string representation, yielding the same result a `repr`
|
|
11
|
+
but catches all exceptions and returns 'N/A' instead of raising the
|
|
12
|
+
exception. Strings may be truncated by providing `clip`.
|
|
13
|
+
|
|
14
|
+
>>> safe_repr(42)
|
|
15
|
+
'42'
|
|
16
|
+
>>> safe_repr('Clipped text', clip=8)
|
|
17
|
+
'Clip..xt'
|
|
18
|
+
>>> safe_repr([1,2,3,4], clip=8)
|
|
19
|
+
'[1,2..4]'
|
|
20
|
+
"""
|
|
21
|
+
try:
|
|
22
|
+
s = repr(obj)
|
|
23
|
+
if not clip or len(s) <= clip:
|
|
24
|
+
return s
|
|
25
|
+
else:
|
|
26
|
+
return s[:clip - 4] + '..' + s[-2:]
|
|
27
|
+
except:
|
|
28
|
+
return 'N/A'
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def trunc(obj: str, max: int, left: bool = False) -> str:
|
|
32
|
+
"""
|
|
33
|
+
Convert `obj` to string, eliminate newlines and truncate the string to
|
|
34
|
+
`max` characters. If there are more characters in the string add ``...`` to
|
|
35
|
+
the string. With `left=True`, the string can be truncated at the beginning.
|
|
36
|
+
|
|
37
|
+
@note: Does not catch exceptions when converting `obj` to string with
|
|
38
|
+
`str`.
|
|
39
|
+
|
|
40
|
+
>>> trunc('This is a long text.', 8)
|
|
41
|
+
This ...
|
|
42
|
+
>>> trunc('This is a long text.', 8, left=True)
|
|
43
|
+
...text.
|
|
44
|
+
"""
|
|
45
|
+
s = str(obj)
|
|
46
|
+
s = s.replace('\n', '|')
|
|
47
|
+
if len(s) > max:
|
|
48
|
+
if left:
|
|
49
|
+
return '...' + s[len(s) - max + 3:]
|
|
50
|
+
else:
|
|
51
|
+
return s[:(max - 3)] + '...'
|
|
52
|
+
else:
|
|
53
|
+
return s
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def pp(i: Union[int, float], base: int = 1024) -> str:
|
|
57
|
+
"""
|
|
58
|
+
Pretty-print the integer `i` as a human-readable size representation.
|
|
59
|
+
"""
|
|
60
|
+
degree = 0
|
|
61
|
+
pattern = "%4d %s"
|
|
62
|
+
while i > base:
|
|
63
|
+
pattern = "%7.2f %s"
|
|
64
|
+
i = i / float(base)
|
|
65
|
+
degree += 1
|
|
66
|
+
scales = ['B', 'KB', 'MB', 'GB', 'TB', 'EB']
|
|
67
|
+
return pattern % (i, scales[degree])
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def pp_timestamp(t: Optional[float]) -> str:
|
|
71
|
+
"""
|
|
72
|
+
Get a friendly timestamp represented as a string.
|
|
73
|
+
"""
|
|
74
|
+
if t is None:
|
|
75
|
+
return ''
|
|
76
|
+
h, m, s = int(t / 3600), int(t / 60 % 60), t % 60
|
|
77
|
+
return "%02d:%02d:%05.2f" % (h, m, s)
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module provides a web-based memory profiling interface. The Pympler web
|
|
3
|
+
frontend exposes process information, tracker statistics, and garbage graphs.
|
|
4
|
+
The web frontend uses `Bottle <http://bottlepy.org>`_, a lightweight Python
|
|
5
|
+
web framework. Bottle is packaged with Pympler.
|
|
6
|
+
|
|
7
|
+
The web server can be invoked almost as easily as setting a breakpoint using
|
|
8
|
+
*pdb*::
|
|
9
|
+
|
|
10
|
+
from pympler.web import start_profiler
|
|
11
|
+
start_profiler()
|
|
12
|
+
|
|
13
|
+
Calling ``start_profiler`` suspends the current thread and executes the Pympler
|
|
14
|
+
web server, exposing profiling data and various facilities of the Pympler
|
|
15
|
+
library via a graphic interface.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import sys
|
|
19
|
+
import os
|
|
20
|
+
import threading
|
|
21
|
+
|
|
22
|
+
from inspect import getouterframes
|
|
23
|
+
from json import dumps
|
|
24
|
+
from shutil import rmtree
|
|
25
|
+
from tempfile import mkdtemp
|
|
26
|
+
from threading import Thread
|
|
27
|
+
from weakref import WeakValueDictionary
|
|
28
|
+
from wsgiref.simple_server import make_server
|
|
29
|
+
|
|
30
|
+
from . import asizeof
|
|
31
|
+
from .garbagegraph import GarbageGraph
|
|
32
|
+
from .process import get_current_threads, ProcessMemoryInfo
|
|
33
|
+
|
|
34
|
+
from .util.stringutils import safe_repr
|
|
35
|
+
|
|
36
|
+
# Prefer the installed version of bottle.py. If bottle.py is not installed
|
|
37
|
+
# fallback to the vendored version.
|
|
38
|
+
try:
|
|
39
|
+
import bottle
|
|
40
|
+
except ImportError:
|
|
41
|
+
from .util import bottle
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ServerState(threading.local):
|
|
45
|
+
"""
|
|
46
|
+
Represents the state of a running server. Needs to be thread local so
|
|
47
|
+
multiple servers can be started in different threads without interfering
|
|
48
|
+
with each other.
|
|
49
|
+
|
|
50
|
+
Cache internal structures (garbage graphs, tracker statistics).
|
|
51
|
+
"""
|
|
52
|
+
def __init__(self):
|
|
53
|
+
self.server = None
|
|
54
|
+
self.stats = None
|
|
55
|
+
self.garbage_graphs = None
|
|
56
|
+
self.id2ref = WeakValueDictionary()
|
|
57
|
+
self.id2obj = dict()
|
|
58
|
+
|
|
59
|
+
def clear_cache(self):
|
|
60
|
+
self.garbage_graphs = None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
server = ServerState()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def get_ref(obj):
|
|
67
|
+
"""
|
|
68
|
+
Get string reference to object. Stores a weak reference in a dictionary
|
|
69
|
+
using the object's id as the key. If the object cannot be weakly
|
|
70
|
+
referenced (e.g. dictionaries, frame objects), store a strong references
|
|
71
|
+
in a classic dictionary.
|
|
72
|
+
|
|
73
|
+
Returns the object's id as a string.
|
|
74
|
+
"""
|
|
75
|
+
oid = id(obj)
|
|
76
|
+
try:
|
|
77
|
+
server.id2ref[oid] = obj
|
|
78
|
+
except TypeError:
|
|
79
|
+
server.id2obj[oid] = obj
|
|
80
|
+
return str(oid)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def get_obj(ref):
|
|
84
|
+
"""Get object from string reference."""
|
|
85
|
+
oid = int(ref)
|
|
86
|
+
return server.id2ref.get(oid) or server.id2obj[oid]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
pympler_path = os.path.dirname(os.path.abspath(__file__))
|
|
90
|
+
static_files = os.path.join(pympler_path, 'templates')
|
|
91
|
+
|
|
92
|
+
bottle.TEMPLATE_PATH.append(static_files)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@bottle.route('/')
|
|
96
|
+
@bottle.view('index')
|
|
97
|
+
def root():
|
|
98
|
+
"""Get overview."""
|
|
99
|
+
pmi = ProcessMemoryInfo()
|
|
100
|
+
return dict(processinfo=pmi)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@bottle.route('/process')
|
|
104
|
+
@bottle.view('process')
|
|
105
|
+
def process():
|
|
106
|
+
"""Get process overview."""
|
|
107
|
+
pmi = ProcessMemoryInfo()
|
|
108
|
+
threads = get_current_threads()
|
|
109
|
+
return dict(info=pmi, threads=threads)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@bottle.route('/tracker')
|
|
113
|
+
@bottle.view('tracker')
|
|
114
|
+
def tracker_index():
|
|
115
|
+
"""Get tracker overview."""
|
|
116
|
+
stats = server.stats
|
|
117
|
+
if stats and stats.snapshots:
|
|
118
|
+
stats.annotate()
|
|
119
|
+
timeseries = []
|
|
120
|
+
for cls in stats.tracked_classes:
|
|
121
|
+
series = []
|
|
122
|
+
for snapshot in stats.snapshots:
|
|
123
|
+
series.append(snapshot.classes.get(cls, {}).get('sum', 0))
|
|
124
|
+
timeseries.append((cls, series))
|
|
125
|
+
|
|
126
|
+
series = [s.overhead for s in stats.snapshots]
|
|
127
|
+
timeseries.append(("Profiling overhead", series))
|
|
128
|
+
|
|
129
|
+
if stats.snapshots[0].system_total.data_segment:
|
|
130
|
+
# Assume tracked data resides in the data segment
|
|
131
|
+
series = [s.system_total.data_segment - s.tracked_total - s.overhead
|
|
132
|
+
for s in stats.snapshots]
|
|
133
|
+
timeseries.append(("Data segment", series))
|
|
134
|
+
series = [s.system_total.code_segment for s in stats.snapshots]
|
|
135
|
+
timeseries.append(("Code segment", series))
|
|
136
|
+
series = [s.system_total.stack_segment for s in stats.snapshots]
|
|
137
|
+
timeseries.append(("Stack segment", series))
|
|
138
|
+
series = [s.system_total.shared_segment for s in stats.snapshots]
|
|
139
|
+
timeseries.append(("Shared memory", series))
|
|
140
|
+
else:
|
|
141
|
+
series = [s.total - s.tracked_total - s.overhead
|
|
142
|
+
for s in stats.snapshots]
|
|
143
|
+
timeseries.append(("Other", series))
|
|
144
|
+
timeseries = [dict(label=label, data=list(enumerate(data)))
|
|
145
|
+
for label, data in timeseries]
|
|
146
|
+
return dict(snapshots=stats.snapshots, timeseries=dumps(timeseries))
|
|
147
|
+
else:
|
|
148
|
+
return dict(snapshots=[])
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@bottle.route('/tracker/class/<clsname>')
|
|
152
|
+
@bottle.view('tracker_class')
|
|
153
|
+
def tracker_class(clsname):
|
|
154
|
+
"""Get class instance details."""
|
|
155
|
+
stats = server.stats
|
|
156
|
+
if not stats:
|
|
157
|
+
bottle.redirect('/tracker')
|
|
158
|
+
stats.annotate()
|
|
159
|
+
return dict(stats=stats, clsname=clsname)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@bottle.route('/refresh')
|
|
163
|
+
def refresh():
|
|
164
|
+
"""Clear all cached information."""
|
|
165
|
+
server.clear_cache()
|
|
166
|
+
bottle.redirect('/')
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@bottle.route('/traceback/<threadid>')
|
|
170
|
+
@bottle.view('stacktrace')
|
|
171
|
+
def get_traceback(threadid):
|
|
172
|
+
threadid = int(threadid)
|
|
173
|
+
frames = sys._current_frames()
|
|
174
|
+
if threadid in frames:
|
|
175
|
+
frame = frames[threadid]
|
|
176
|
+
stack = getouterframes(frame, 5)
|
|
177
|
+
stack.reverse()
|
|
178
|
+
stack = [(get_ref(f[0].f_locals),) + f[1:] for f in stack]
|
|
179
|
+
else:
|
|
180
|
+
stack = []
|
|
181
|
+
return dict(stack=stack, threadid=threadid)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@bottle.route('/objects/<oid>')
|
|
185
|
+
@bottle.view('referents')
|
|
186
|
+
def get_obj_referents(oid):
|
|
187
|
+
referents = {}
|
|
188
|
+
obj = get_obj(oid)
|
|
189
|
+
if type(obj) is dict:
|
|
190
|
+
named_objects = asizeof.named_refs(obj)
|
|
191
|
+
else:
|
|
192
|
+
refs = asizeof._getreferents(obj)
|
|
193
|
+
named_objects = [(repr(type(x)), x) for x in refs]
|
|
194
|
+
for name, o in named_objects:
|
|
195
|
+
referents[name] = (get_ref(o), type(o).__name__,
|
|
196
|
+
safe_repr(o, clip=48), asizeof.asizeof(o))
|
|
197
|
+
return dict(referents=referents)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@bottle.route('/static/<filename>')
|
|
201
|
+
def static_file(filename):
|
|
202
|
+
"""Get static files (CSS-files)."""
|
|
203
|
+
return bottle.static_file(filename, root=static_files)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _compute_garbage_graphs():
|
|
207
|
+
"""
|
|
208
|
+
Retrieve garbage graph objects from cache, compute if cache is cold.
|
|
209
|
+
"""
|
|
210
|
+
if server.garbage_graphs is None:
|
|
211
|
+
server.garbage_graphs = GarbageGraph().split_and_sort()
|
|
212
|
+
return server.garbage_graphs
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
@bottle.route('/garbage')
|
|
216
|
+
@bottle.view('garbage_index')
|
|
217
|
+
def garbage_index():
|
|
218
|
+
"""Get garbage overview."""
|
|
219
|
+
garbage_graphs = _compute_garbage_graphs()
|
|
220
|
+
return dict(graphs=garbage_graphs)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@bottle.route('/garbage/<index:int>')
|
|
224
|
+
@bottle.view('garbage')
|
|
225
|
+
def garbage_cycle(index):
|
|
226
|
+
"""Get reference cycle details."""
|
|
227
|
+
graph = _compute_garbage_graphs()[int(index)]
|
|
228
|
+
graph.reduce_to_cycles()
|
|
229
|
+
objects = graph.metadata
|
|
230
|
+
objects.sort(key=lambda x: -x.size)
|
|
231
|
+
return dict(objects=objects, index=index)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _get_graph(graph, filename):
|
|
235
|
+
"""Retrieve or render a graph."""
|
|
236
|
+
try:
|
|
237
|
+
rendered = graph.rendered_file
|
|
238
|
+
except AttributeError:
|
|
239
|
+
try:
|
|
240
|
+
graph.render(os.path.join(server.tmpdir, filename), format='png')
|
|
241
|
+
rendered = filename
|
|
242
|
+
except OSError:
|
|
243
|
+
rendered = None
|
|
244
|
+
graph.rendered_file = rendered
|
|
245
|
+
return rendered
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
@bottle.route('/garbage/graph/<index:int>')
|
|
249
|
+
def garbage_graph(index):
|
|
250
|
+
"""Get graph representation of reference cycle."""
|
|
251
|
+
graph = _compute_garbage_graphs()[int(index)]
|
|
252
|
+
reduce_graph = bottle.request.GET.get('reduce', '')
|
|
253
|
+
if reduce_graph:
|
|
254
|
+
graph = graph.reduce_to_cycles()
|
|
255
|
+
if not graph:
|
|
256
|
+
return None
|
|
257
|
+
filename = 'garbage%so%s.png' % (index, reduce_graph)
|
|
258
|
+
rendered_file = _get_graph(graph, filename)
|
|
259
|
+
if rendered_file:
|
|
260
|
+
return bottle.static_file(rendered_file, root=server.tmpdir)
|
|
261
|
+
else:
|
|
262
|
+
return None
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
@bottle.route('/help')
|
|
266
|
+
def show_documentation():
|
|
267
|
+
"""Redirect to online documentation."""
|
|
268
|
+
bottle.redirect('https://pympler.readthedocs.io/en/latest/')
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
class PymplerServer(bottle.ServerAdapter):
|
|
272
|
+
"""Simple WSGI server."""
|
|
273
|
+
def run(self, handler):
|
|
274
|
+
self.server = make_server(self.host, self.port, handler)
|
|
275
|
+
self.server.serve_forever()
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def start_profiler(host='localhost', port=8090, tracker=None, stats=None,
|
|
279
|
+
debug=False, **kwargs):
|
|
280
|
+
"""
|
|
281
|
+
Start the web server to show profiling data. The function suspends the
|
|
282
|
+
Python application (the current thread) until the web server is stopped.
|
|
283
|
+
|
|
284
|
+
The only way to stop the server is to signal the running thread, e.g. press
|
|
285
|
+
Ctrl+C in the console. If this isn't feasible for your application use
|
|
286
|
+
`start_in_background` instead.
|
|
287
|
+
|
|
288
|
+
During the execution of the web server, profiling data is (lazily) cached
|
|
289
|
+
to improve performance. For example, garbage graphs are rendered when the
|
|
290
|
+
garbage profiling data is requested and are simply retransmitted upon later
|
|
291
|
+
requests.
|
|
292
|
+
|
|
293
|
+
The web server can display profiling data from previously taken snapshots
|
|
294
|
+
when `tracker` or `stats` is specified. The former is useful for profiling
|
|
295
|
+
a running application, the latter for off-line analysis. Requires existing
|
|
296
|
+
snapshots taken with
|
|
297
|
+
:py:meth:`~pympler.classtracker.ClassTracker.create_snapshot` or
|
|
298
|
+
:py:meth:`~pympler.classtracker.ClassTracker.start_periodic_snapshots`.
|
|
299
|
+
|
|
300
|
+
:param host: the host where the server shall run, default is localhost
|
|
301
|
+
:param port: server listens on the specified port, default is 8090 to allow
|
|
302
|
+
coexistance with common web applications
|
|
303
|
+
:param tracker: `ClassTracker` instance, browse profiling data (on-line
|
|
304
|
+
analysis)
|
|
305
|
+
:param stats: `Stats` instance, analyze `ClassTracker` profiling dumps
|
|
306
|
+
(useful for off-line analysis)
|
|
307
|
+
"""
|
|
308
|
+
if tracker and not stats:
|
|
309
|
+
server.stats = tracker.stats
|
|
310
|
+
else:
|
|
311
|
+
server.stats = stats
|
|
312
|
+
try:
|
|
313
|
+
server.tmpdir = mkdtemp(prefix='pympler')
|
|
314
|
+
server.server = PymplerServer(host=host, port=port, **kwargs)
|
|
315
|
+
bottle.debug(debug)
|
|
316
|
+
bottle.run(server=server.server)
|
|
317
|
+
finally:
|
|
318
|
+
rmtree(server.tmpdir)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
class ProfilerThread(Thread):
|
|
322
|
+
"""Encapsulates a thread to run the web server."""
|
|
323
|
+
def __init__(self, group=None, target=None, name='Pympler web frontend',
|
|
324
|
+
**kwargs):
|
|
325
|
+
super(ProfilerThread, self).__init__(group=group,
|
|
326
|
+
target=target,
|
|
327
|
+
name=name)
|
|
328
|
+
self.kwargs = kwargs
|
|
329
|
+
self.daemon = True
|
|
330
|
+
|
|
331
|
+
def run(self):
|
|
332
|
+
start_profiler(**self.kwargs)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def start_in_background(**kwargs):
|
|
336
|
+
"""
|
|
337
|
+
Start the web server in the background. A new thread is created which
|
|
338
|
+
serves the profiling interface without suspending the current application.
|
|
339
|
+
|
|
340
|
+
For the documentation of the parameters see `start_profiler`.
|
|
341
|
+
|
|
342
|
+
Returns the created thread object.
|
|
343
|
+
"""
|
|
344
|
+
thread = ProfilerThread(**kwargs)
|
|
345
|
+
thread.start()
|
|
346
|
+
return thread
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
werkzeug
|
|
3
|
+
~~~~~~~~
|
|
4
|
+
|
|
5
|
+
Werkzeug is the Swiss Army knife of Python web development.
|
|
6
|
+
|
|
7
|
+
It provides useful classes and functions for any WSGI application to
|
|
8
|
+
make the life of a Python web developer much easier. All of the provided
|
|
9
|
+
classes are independent from each other so you can mix it with any other
|
|
10
|
+
library.
|
|
11
|
+
|
|
12
|
+
:copyright: 2007 Pallets
|
|
13
|
+
:license: BSD-3-Clause
|
|
14
|
+
"""
|
|
15
|
+
from .serving import run_simple
|
|
16
|
+
from .test import Client
|
|
17
|
+
from .wrappers import Request
|
|
18
|
+
from .wrappers import Response
|
|
19
|
+
|
|
20
|
+
__version__ = "1.0.1"
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# flake8: noqa
|
|
2
|
+
# This whole file is full of lint errors
|
|
3
|
+
import functools
|
|
4
|
+
import operator
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
import builtins
|
|
9
|
+
except ImportError:
|
|
10
|
+
import __builtin__ as builtins
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
PY2 = sys.version_info[0] == 2
|
|
14
|
+
WIN = sys.platform.startswith("win")
|
|
15
|
+
|
|
16
|
+
_identity = lambda x: x
|
|
17
|
+
|
|
18
|
+
if PY2:
|
|
19
|
+
unichr = unichr
|
|
20
|
+
text_type = unicode
|
|
21
|
+
string_types = (str, unicode)
|
|
22
|
+
integer_types = (int, long)
|
|
23
|
+
|
|
24
|
+
iterkeys = lambda d, *args, **kwargs: d.iterkeys(*args, **kwargs)
|
|
25
|
+
itervalues = lambda d, *args, **kwargs: d.itervalues(*args, **kwargs)
|
|
26
|
+
iteritems = lambda d, *args, **kwargs: d.iteritems(*args, **kwargs)
|
|
27
|
+
|
|
28
|
+
iterlists = lambda d, *args, **kwargs: d.iterlists(*args, **kwargs)
|
|
29
|
+
iterlistvalues = lambda d, *args, **kwargs: d.iterlistvalues(*args, **kwargs)
|
|
30
|
+
|
|
31
|
+
int_to_byte = chr
|
|
32
|
+
iter_bytes = iter
|
|
33
|
+
|
|
34
|
+
import collections as collections_abc
|
|
35
|
+
|
|
36
|
+
exec("def reraise(tp, value, tb=None):\n raise tp, value, tb")
|
|
37
|
+
|
|
38
|
+
def fix_tuple_repr(obj):
|
|
39
|
+
def __repr__(self):
|
|
40
|
+
cls = self.__class__
|
|
41
|
+
return "%s(%s)" % (
|
|
42
|
+
cls.__name__,
|
|
43
|
+
", ".join(
|
|
44
|
+
"%s=%r" % (field, self[index])
|
|
45
|
+
for index, field in enumerate(cls._fields)
|
|
46
|
+
),
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
obj.__repr__ = __repr__
|
|
50
|
+
return obj
|
|
51
|
+
|
|
52
|
+
def implements_iterator(cls):
|
|
53
|
+
cls.next = cls.__next__
|
|
54
|
+
del cls.__next__
|
|
55
|
+
return cls
|
|
56
|
+
|
|
57
|
+
def implements_to_string(cls):
|
|
58
|
+
cls.__unicode__ = cls.__str__
|
|
59
|
+
cls.__str__ = lambda x: x.__unicode__().encode("utf-8")
|
|
60
|
+
return cls
|
|
61
|
+
|
|
62
|
+
def native_string_result(func):
|
|
63
|
+
def wrapper(*args, **kwargs):
|
|
64
|
+
return func(*args, **kwargs).encode("utf-8")
|
|
65
|
+
|
|
66
|
+
return functools.update_wrapper(wrapper, func)
|
|
67
|
+
|
|
68
|
+
def implements_bool(cls):
|
|
69
|
+
cls.__nonzero__ = cls.__bool__
|
|
70
|
+
del cls.__bool__
|
|
71
|
+
return cls
|
|
72
|
+
|
|
73
|
+
from itertools import imap, izip, ifilter
|
|
74
|
+
|
|
75
|
+
range_type = xrange
|
|
76
|
+
|
|
77
|
+
from StringIO import StringIO
|
|
78
|
+
from cStringIO import StringIO as BytesIO
|
|
79
|
+
|
|
80
|
+
NativeStringIO = BytesIO
|
|
81
|
+
|
|
82
|
+
def make_literal_wrapper(reference):
|
|
83
|
+
return _identity
|
|
84
|
+
|
|
85
|
+
def normalize_string_tuple(tup):
|
|
86
|
+
"""Normalizes a string tuple to a common type. Following Python 2
|
|
87
|
+
rules, upgrades to unicode are implicit.
|
|
88
|
+
"""
|
|
89
|
+
if any(isinstance(x, text_type) for x in tup):
|
|
90
|
+
return tuple(to_unicode(x) for x in tup)
|
|
91
|
+
return tup
|
|
92
|
+
|
|
93
|
+
def try_coerce_native(s):
|
|
94
|
+
"""Try to coerce a unicode string to native if possible. Otherwise,
|
|
95
|
+
leave it as unicode.
|
|
96
|
+
"""
|
|
97
|
+
try:
|
|
98
|
+
return to_native(s)
|
|
99
|
+
except UnicodeError:
|
|
100
|
+
return s
|
|
101
|
+
|
|
102
|
+
wsgi_get_bytes = _identity
|
|
103
|
+
|
|
104
|
+
def wsgi_decoding_dance(s, charset="utf-8", errors="replace"):
|
|
105
|
+
return s.decode(charset, errors)
|
|
106
|
+
|
|
107
|
+
def wsgi_encoding_dance(s, charset="utf-8", errors="replace"):
|
|
108
|
+
if isinstance(s, bytes):
|
|
109
|
+
return s
|
|
110
|
+
return s.encode(charset, errors)
|
|
111
|
+
|
|
112
|
+
def to_bytes(x, charset=sys.getdefaultencoding(), errors="strict"):
|
|
113
|
+
if x is None:
|
|
114
|
+
return None
|
|
115
|
+
if isinstance(x, (bytes, bytearray, buffer)):
|
|
116
|
+
return bytes(x)
|
|
117
|
+
if isinstance(x, unicode):
|
|
118
|
+
return x.encode(charset, errors)
|
|
119
|
+
raise TypeError("Expected bytes")
|
|
120
|
+
|
|
121
|
+
def to_native(x, charset=sys.getdefaultencoding(), errors="strict"):
|
|
122
|
+
if x is None or isinstance(x, str):
|
|
123
|
+
return x
|
|
124
|
+
return x.encode(charset, errors)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
else:
|
|
128
|
+
unichr = chr
|
|
129
|
+
text_type = str
|
|
130
|
+
string_types = (str,)
|
|
131
|
+
integer_types = (int,)
|
|
132
|
+
|
|
133
|
+
iterkeys = lambda d, *args, **kwargs: iter(d.keys(*args, **kwargs))
|
|
134
|
+
itervalues = lambda d, *args, **kwargs: iter(d.values(*args, **kwargs))
|
|
135
|
+
iteritems = lambda d, *args, **kwargs: iter(d.items(*args, **kwargs))
|
|
136
|
+
|
|
137
|
+
iterlists = lambda d, *args, **kwargs: iter(d.lists(*args, **kwargs))
|
|
138
|
+
iterlistvalues = lambda d, *args, **kwargs: iter(d.listvalues(*args, **kwargs))
|
|
139
|
+
|
|
140
|
+
int_to_byte = operator.methodcaller("to_bytes", 1, "big")
|
|
141
|
+
iter_bytes = functools.partial(map, int_to_byte)
|
|
142
|
+
|
|
143
|
+
import collections.abc as collections_abc
|
|
144
|
+
|
|
145
|
+
def reraise(tp, value, tb=None):
|
|
146
|
+
if value.__traceback__ is not tb:
|
|
147
|
+
raise value.with_traceback(tb)
|
|
148
|
+
raise value
|
|
149
|
+
|
|
150
|
+
fix_tuple_repr = _identity
|
|
151
|
+
implements_iterator = _identity
|
|
152
|
+
implements_to_string = _identity
|
|
153
|
+
implements_bool = _identity
|
|
154
|
+
native_string_result = _identity
|
|
155
|
+
imap = map
|
|
156
|
+
izip = zip
|
|
157
|
+
ifilter = filter
|
|
158
|
+
range_type = range
|
|
159
|
+
|
|
160
|
+
from io import StringIO, BytesIO
|
|
161
|
+
|
|
162
|
+
NativeStringIO = StringIO
|
|
163
|
+
|
|
164
|
+
_latin1_encode = operator.methodcaller("encode", "latin1")
|
|
165
|
+
|
|
166
|
+
def make_literal_wrapper(reference):
|
|
167
|
+
if isinstance(reference, text_type):
|
|
168
|
+
return _identity
|
|
169
|
+
return _latin1_encode
|
|
170
|
+
|
|
171
|
+
def normalize_string_tuple(tup):
|
|
172
|
+
"""Ensures that all types in the tuple are either strings
|
|
173
|
+
or bytes.
|
|
174
|
+
"""
|
|
175
|
+
tupiter = iter(tup)
|
|
176
|
+
is_text = isinstance(next(tupiter, None), text_type)
|
|
177
|
+
for arg in tupiter:
|
|
178
|
+
if isinstance(arg, text_type) != is_text:
|
|
179
|
+
raise TypeError(
|
|
180
|
+
"Cannot mix str and bytes arguments (got %s)" % repr(tup)
|
|
181
|
+
)
|
|
182
|
+
return tup
|
|
183
|
+
|
|
184
|
+
try_coerce_native = _identity
|
|
185
|
+
wsgi_get_bytes = _latin1_encode
|
|
186
|
+
|
|
187
|
+
def wsgi_decoding_dance(s, charset="utf-8", errors="replace"):
|
|
188
|
+
return s.encode("latin1").decode(charset, errors)
|
|
189
|
+
|
|
190
|
+
def wsgi_encoding_dance(s, charset="utf-8", errors="replace"):
|
|
191
|
+
if isinstance(s, text_type):
|
|
192
|
+
s = s.encode(charset)
|
|
193
|
+
return s.decode("latin1", errors)
|
|
194
|
+
|
|
195
|
+
def to_bytes(x, charset=sys.getdefaultencoding(), errors="strict"):
|
|
196
|
+
if x is None:
|
|
197
|
+
return None
|
|
198
|
+
if isinstance(x, (bytes, bytearray, memoryview)): # noqa
|
|
199
|
+
return bytes(x)
|
|
200
|
+
if isinstance(x, str):
|
|
201
|
+
return x.encode(charset, errors)
|
|
202
|
+
raise TypeError("Expected bytes")
|
|
203
|
+
|
|
204
|
+
def to_native(x, charset=sys.getdefaultencoding(), errors="strict"):
|
|
205
|
+
if x is None or isinstance(x, str):
|
|
206
|
+
return x
|
|
207
|
+
return x.decode(charset, errors)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def to_unicode(
|
|
211
|
+
x, charset=sys.getdefaultencoding(), errors="strict", allow_none_charset=False
|
|
212
|
+
):
|
|
213
|
+
if x is None:
|
|
214
|
+
return None
|
|
215
|
+
if not isinstance(x, bytes):
|
|
216
|
+
return text_type(x)
|
|
217
|
+
if charset is None and allow_none_charset:
|
|
218
|
+
return x
|
|
219
|
+
return x.decode(charset, errors)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
try:
|
|
223
|
+
from os import fspath
|
|
224
|
+
except ImportError:
|
|
225
|
+
# Python < 3.6
|
|
226
|
+
# https://www.python.org/dev/peps/pep-0519/#backwards-compatibility
|
|
227
|
+
def fspath(path):
|
|
228
|
+
return path.__fspath__() if hasattr(path, "__fspath__") else path
|