c2cwsgiutils 5.1.7.dev20230901073305__py3-none-any.whl → 5.2.1.dev197__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.
- c2cwsgiutils/__init__.py +13 -13
- c2cwsgiutils/acceptance/connection.py +5 -2
- c2cwsgiutils/acceptance/image.py +98 -4
- c2cwsgiutils/acceptance/package-lock.json +1933 -0
- c2cwsgiutils/acceptance/package.json +7 -0
- c2cwsgiutils/acceptance/print.py +4 -4
- c2cwsgiutils/acceptance/screenshot.js +62 -0
- c2cwsgiutils/acceptance/utils.py +14 -22
- c2cwsgiutils/auth.py +4 -4
- c2cwsgiutils/broadcast/__init__.py +15 -7
- c2cwsgiutils/broadcast/interface.py +3 -2
- c2cwsgiutils/broadcast/local.py +3 -2
- c2cwsgiutils/broadcast/redis.py +8 -7
- c2cwsgiutils/client_info.py +5 -5
- c2cwsgiutils/config_utils.py +2 -1
- c2cwsgiutils/coverage_setup.py +2 -2
- c2cwsgiutils/db.py +58 -37
- c2cwsgiutils/db_maintenance_view.py +2 -1
- c2cwsgiutils/debug/_listeners.py +10 -9
- c2cwsgiutils/debug/_views.py +12 -11
- c2cwsgiutils/debug/utils.py +5 -5
- c2cwsgiutils/errors.py +7 -6
- c2cwsgiutils/health_check.py +96 -85
- c2cwsgiutils/index.py +90 -105
- c2cwsgiutils/loader.py +3 -3
- c2cwsgiutils/logging_view.py +3 -2
- c2cwsgiutils/models_graph.py +8 -6
- c2cwsgiutils/prometheus.py +175 -57
- c2cwsgiutils/pyramid.py +4 -2
- c2cwsgiutils/pyramid_logging.py +2 -1
- c2cwsgiutils/redis_stats.py +13 -11
- c2cwsgiutils/redis_utils.py +15 -14
- c2cwsgiutils/request_tracking/__init__.py +36 -30
- c2cwsgiutils/request_tracking/_sql.py +3 -1
- c2cwsgiutils/scripts/genversion.py +4 -4
- c2cwsgiutils/scripts/stats_db.py +130 -68
- c2cwsgiutils/scripts/test_print.py +1 -1
- c2cwsgiutils/sentry.py +2 -1
- c2cwsgiutils/setup_process.py +13 -17
- c2cwsgiutils/sql_profiler/_impl.py +12 -5
- c2cwsgiutils/sqlalchemylogger/README.md +48 -0
- c2cwsgiutils/sqlalchemylogger/_models.py +7 -4
- c2cwsgiutils/sqlalchemylogger/examples/example.py +15 -0
- c2cwsgiutils/sqlalchemylogger/handlers.py +11 -8
- c2cwsgiutils/static/favicon-16x16.png +0 -0
- c2cwsgiutils/static/favicon-32x32.png +0 -0
- c2cwsgiutils/stats_pyramid/__init__.py +7 -11
- c2cwsgiutils/stats_pyramid/_db_spy.py +14 -11
- c2cwsgiutils/stats_pyramid/_pyramid_spy.py +29 -20
- c2cwsgiutils/templates/index.html.mako +50 -0
- c2cwsgiutils/version.py +49 -16
- c2cwsgiutils-5.2.1.dev197.dist-info/LICENSE +22 -0
- {c2cwsgiutils-5.1.7.dev20230901073305.dist-info → c2cwsgiutils-5.2.1.dev197.dist-info}/METADATA +187 -135
- c2cwsgiutils-5.2.1.dev197.dist-info/RECORD +67 -0
- {c2cwsgiutils-5.1.7.dev20230901073305.dist-info → c2cwsgiutils-5.2.1.dev197.dist-info}/WHEEL +1 -2
- c2cwsgiutils-5.2.1.dev197.dist-info/entry_points.txt +21 -0
- c2cwsgiutils/acceptance/composition.py +0 -129
- c2cwsgiutils/metrics.py +0 -110
- c2cwsgiutils/scripts/check_es.py +0 -130
- c2cwsgiutils/scripts/coverage_report.py +0 -36
- c2cwsgiutils/stats.py +0 -355
- c2cwsgiutils/stats_pyramid/_views.py +0 -16
- c2cwsgiutils-5.1.7.dev20230901073305.data/scripts/c2cwsgiutils-run +0 -32
- c2cwsgiutils-5.1.7.dev20230901073305.dist-info/LICENSE.txt +0 -28
- c2cwsgiutils-5.1.7.dev20230901073305.dist-info/RECORD +0 -69
- c2cwsgiutils-5.1.7.dev20230901073305.dist-info/entry_points.txt +0 -25
- c2cwsgiutils-5.1.7.dev20230901073305.dist-info/top_level.txt +0 -2
- tests/acceptance/__init__.py +0 -0
- tests/acceptance/test_utils.py +0 -13
c2cwsgiutils/db.py
CHANGED
@@ -2,8 +2,9 @@
|
|
2
2
|
import logging
|
3
3
|
import re
|
4
4
|
import warnings
|
5
|
-
from
|
6
|
-
from
|
5
|
+
from collections.abc import Iterable
|
6
|
+
from re import Pattern
|
7
|
+
from typing import TYPE_CHECKING, Any, Callable, Optional, Union, cast
|
7
8
|
|
8
9
|
import pyramid.config
|
9
10
|
import pyramid.config.settings
|
@@ -13,9 +14,16 @@ import sqlalchemy.orm
|
|
13
14
|
import transaction
|
14
15
|
import zope.sqlalchemy
|
15
16
|
from sqlalchemy import engine_from_config
|
16
|
-
from sqlalchemy.orm import sessionmaker
|
17
17
|
from zope.sqlalchemy import register
|
18
18
|
|
19
|
+
if TYPE_CHECKING:
|
20
|
+
scoped_session = sqlalchemy.orm.scoped_session[sqlalchemy.orm.Session]
|
21
|
+
sessionmaker = sqlalchemy.orm.sessionmaker[sqlalchemy.orm.Session]
|
22
|
+
else:
|
23
|
+
scoped_session = sqlalchemy.orm.scoped_session
|
24
|
+
sessionmaker = sqlalchemy.orm.sessionmaker
|
25
|
+
|
26
|
+
|
19
27
|
LOG = logging.getLogger(__name__)
|
20
28
|
RE_COMPILE: Callable[[str], Pattern[str]] = re.compile
|
21
29
|
|
@@ -35,8 +43,8 @@ def setup_session(
|
|
35
43
|
slave_prefix: Optional[str] = None,
|
36
44
|
force_master: Optional[Iterable[str]] = None,
|
37
45
|
force_slave: Optional[Iterable[str]] = None,
|
38
|
-
) ->
|
39
|
-
Union[sqlalchemy.orm.Session,
|
46
|
+
) -> tuple[
|
47
|
+
Union[sqlalchemy.orm.Session, scoped_session],
|
40
48
|
sqlalchemy.engine.Engine,
|
41
49
|
sqlalchemy.engine.Engine,
|
42
50
|
]:
|
@@ -51,7 +59,7 @@ def setup_session(
|
|
51
59
|
Those parameters are lists of regex that are going to be matched against "{VERB} {PATH}". Warning, the
|
52
60
|
path includes the route_prefix.
|
53
61
|
|
54
|
-
Arguments:
|
62
|
+
Keyword Arguments:
|
55
63
|
|
56
64
|
config: The pyramid Configuration object
|
57
65
|
master_prefix: The prefix for the master connection configuration entries in the application \
|
@@ -68,7 +76,7 @@ def setup_session(
|
|
68
76
|
slave_prefix = master_prefix
|
69
77
|
settings = config.registry.settings
|
70
78
|
rw_engine = sqlalchemy.engine_from_config(settings, master_prefix + ".")
|
71
|
-
rw_engine.c2c_name = master_prefix
|
79
|
+
rw_engine.c2c_name = master_prefix # type: ignore
|
72
80
|
factory = sqlalchemy.orm.sessionmaker(bind=rw_engine)
|
73
81
|
register(factory)
|
74
82
|
db_session = sqlalchemy.orm.scoped_session(factory)
|
@@ -77,14 +85,14 @@ def setup_session(
|
|
77
85
|
if settings[master_prefix + ".url"] != settings.get(slave_prefix + ".url"):
|
78
86
|
LOG.info("Using a slave DB for reading %s", master_prefix)
|
79
87
|
ro_engine = sqlalchemy.engine_from_config(config.get_settings(), slave_prefix + ".")
|
80
|
-
ro_engine.c2c_name = slave_prefix
|
88
|
+
ro_engine.c2c_name = slave_prefix # type: ignore
|
81
89
|
tween_name = master_prefix.replace(".", "_")
|
82
90
|
_add_tween(config, tween_name, db_session, force_master, force_slave)
|
83
91
|
else:
|
84
92
|
ro_engine = rw_engine
|
85
93
|
|
86
|
-
db_session.c2c_rw_bind = rw_engine
|
87
|
-
db_session.c2c_ro_bind = ro_engine
|
94
|
+
db_session.c2c_rw_bind = rw_engine # type: ignore
|
95
|
+
db_session.c2c_ro_bind = ro_engine # type: ignore
|
88
96
|
return db_session, rw_engine, ro_engine
|
89
97
|
|
90
98
|
|
@@ -96,7 +104,7 @@ def create_session(
|
|
96
104
|
force_master: Optional[Iterable[str]] = None,
|
97
105
|
force_slave: Optional[Iterable[str]] = None,
|
98
106
|
**engine_config: Any,
|
99
|
-
) -> Union[sqlalchemy.orm.Session,
|
107
|
+
) -> Union[sqlalchemy.orm.Session, scoped_session]:
|
100
108
|
"""
|
101
109
|
Create a SQLAlchemy session.
|
102
110
|
|
@@ -108,7 +116,7 @@ def create_session(
|
|
108
116
|
Those parameters are lists of regex that are going to be matched against "{VERB} {PATH}". Warning, the
|
109
117
|
path includes the route_prefix.
|
110
118
|
|
111
|
-
Arguments:
|
119
|
+
Keyword Arguments:
|
112
120
|
|
113
121
|
config: The pyramid Configuration object. If None, only master is used
|
114
122
|
name: The name of the check
|
@@ -134,21 +142,21 @@ def create_session(
|
|
134
142
|
LOG.info("Using a slave DB for reading %s", name)
|
135
143
|
ro_engine = sqlalchemy.create_engine(slave_url, **engine_config)
|
136
144
|
_add_tween(config, name, db_session, force_master, force_slave)
|
137
|
-
rw_engine.c2c_name = name + "_master"
|
138
|
-
ro_engine.c2c_name = name + "_slave"
|
145
|
+
rw_engine.c2c_name = name + "_master" # type: ignore
|
146
|
+
ro_engine.c2c_name = name + "_slave" # type: ignore
|
139
147
|
else:
|
140
|
-
rw_engine.c2c_name = name
|
148
|
+
rw_engine.c2c_name = name # type: ignore
|
141
149
|
ro_engine = rw_engine
|
142
150
|
|
143
|
-
db_session.c2c_rw_bind = rw_engine
|
144
|
-
db_session.c2c_ro_bind = ro_engine
|
151
|
+
db_session.c2c_rw_bind = rw_engine # type: ignore
|
152
|
+
db_session.c2c_ro_bind = ro_engine # type: ignore
|
145
153
|
return db_session
|
146
154
|
|
147
155
|
|
148
156
|
def _add_tween(
|
149
157
|
config: pyramid.config.Configurator,
|
150
158
|
name: str,
|
151
|
-
db_session:
|
159
|
+
db_session: scoped_session,
|
152
160
|
force_master: Optional[Iterable[str]],
|
153
161
|
force_slave: Optional[Iterable[str]],
|
154
162
|
) -> None:
|
@@ -177,11 +185,19 @@ def _add_tween(
|
|
177
185
|
not has_force_master
|
178
186
|
and (request.method in ("GET", "OPTIONS") or any(r.match(method_path) for r in slave_paths))
|
179
187
|
):
|
180
|
-
LOG.debug(
|
181
|
-
|
188
|
+
LOG.debug(
|
189
|
+
"Using %s database for: %s",
|
190
|
+
db_session.c2c_ro_bind.c2c_name, # type: ignore
|
191
|
+
method_path,
|
192
|
+
)
|
193
|
+
session.bind = db_session.c2c_ro_bind # type: ignore
|
182
194
|
else:
|
183
|
-
LOG.debug(
|
184
|
-
|
195
|
+
LOG.debug(
|
196
|
+
"Using %s database for: %s",
|
197
|
+
db_session.c2c_rw_bind.c2c_name, # type: ignore
|
198
|
+
method_path,
|
199
|
+
)
|
200
|
+
session.bind = db_session.c2c_rw_bind # type: ignore
|
185
201
|
|
186
202
|
try:
|
187
203
|
return handler(request)
|
@@ -194,7 +210,7 @@ def _add_tween(
|
|
194
210
|
config.add_tween("c2cwsgiutils.db.tweens." + name, over="pyramid_tm.tm_tween_factory")
|
195
211
|
|
196
212
|
|
197
|
-
class SessionFactory(sessionmaker):
|
213
|
+
class SessionFactory(sessionmaker):
|
198
214
|
"""The custom session factory that manage the read only and read write sessions."""
|
199
215
|
|
200
216
|
def __init__(
|
@@ -214,18 +230,18 @@ class SessionFactory(sessionmaker): # type: ignore
|
|
214
230
|
|
215
231
|
def engine_name(self, readwrite: bool) -> str:
|
216
232
|
if readwrite:
|
217
|
-
return cast(str, self.rw_engine.c2c_name)
|
218
|
-
return cast(str, self.ro_engine.c2c_name)
|
233
|
+
return cast(str, self.rw_engine.c2c_name) # type: ignore
|
234
|
+
return cast(str, self.ro_engine.c2c_name) # type: ignore
|
219
235
|
|
220
|
-
def __call__(
|
236
|
+
def __call__( # type: ignore
|
221
237
|
self, request: Optional[pyramid.request.Request], readwrite: Optional[bool] = None, **local_kw: Any
|
222
|
-
) ->
|
238
|
+
) -> scoped_session:
|
223
239
|
if readwrite is not None:
|
224
240
|
if readwrite and not force_readonly:
|
225
|
-
LOG.debug("Using %s database", self.rw_engine.c2c_name)
|
241
|
+
LOG.debug("Using %s database", self.rw_engine.c2c_name) # type: ignore
|
226
242
|
self.configure(bind=self.rw_engine)
|
227
243
|
else:
|
228
|
-
LOG.debug("Using %s database", self.ro_engine.c2c_name)
|
244
|
+
LOG.debug("Using %s database", self.ro_engine.c2c_name) # type: ignore
|
229
245
|
self.configure(bind=self.ro_engine)
|
230
246
|
else:
|
231
247
|
assert request is not None
|
@@ -238,12 +254,12 @@ class SessionFactory(sessionmaker): # type: ignore
|
|
238
254
|
or any(r.match(method_path) for r in self.slave_paths)
|
239
255
|
)
|
240
256
|
):
|
241
|
-
LOG.debug("Using %s database for: %s", self.ro_engine.c2c_name, method_path)
|
257
|
+
LOG.debug("Using %s database for: %s", self.ro_engine.c2c_name, method_path) # type: ignore
|
242
258
|
self.configure(bind=self.ro_engine)
|
243
259
|
else:
|
244
|
-
LOG.debug("Using %s database for: %s", self.rw_engine.c2c_name, method_path)
|
260
|
+
LOG.debug("Using %s database for: %s", self.rw_engine.c2c_name, method_path) # type: ignore
|
245
261
|
self.configure(bind=self.rw_engine)
|
246
|
-
return super().__call__(**local_kw)
|
262
|
+
return super().__call__(**local_kw) # type: ignore
|
247
263
|
|
248
264
|
|
249
265
|
def get_engine(
|
@@ -253,7 +269,9 @@ def get_engine(
|
|
253
269
|
return engine_from_config(settings, prefix)
|
254
270
|
|
255
271
|
|
256
|
-
def get_session_factory(
|
272
|
+
def get_session_factory(
|
273
|
+
engine: sqlalchemy.engine.Engine,
|
274
|
+
) -> sessionmaker:
|
257
275
|
"""Get the session factory from the engine."""
|
258
276
|
factory = sessionmaker()
|
259
277
|
factory.configure(bind=engine)
|
@@ -320,6 +338,7 @@ def get_tm_session(
|
|
320
338
|
request = dbsession.info["request"]
|
321
339
|
"""
|
322
340
|
dbsession = session_factory()
|
341
|
+
assert isinstance(dbsession, sqlalchemy.orm.Session), type(dbsession)
|
323
342
|
zope.sqlalchemy.register(dbsession, transaction_manager=transaction_manager)
|
324
343
|
return dbsession
|
325
344
|
|
@@ -328,7 +347,7 @@ def get_tm_session_pyramid(
|
|
328
347
|
session_factory: SessionFactory,
|
329
348
|
transaction_manager: transaction.TransactionManager,
|
330
349
|
request: pyramid.request.Request,
|
331
|
-
) ->
|
350
|
+
) -> scoped_session:
|
332
351
|
"""
|
333
352
|
Get a ``sqlalchemy.orm.Session`` instance backed by a transaction.
|
334
353
|
|
@@ -349,7 +368,7 @@ def init(
|
|
349
368
|
"""
|
350
369
|
Initialize the database for a Pyramid app.
|
351
370
|
|
352
|
-
Arguments:
|
371
|
+
Keyword Arguments:
|
353
372
|
|
354
373
|
config: The pyramid Configuration object
|
355
374
|
master_prefix: The prefix for the master connection configuration entries in the application \
|
@@ -368,13 +387,13 @@ def init(
|
|
368
387
|
dbengine = settings.get("dbengine")
|
369
388
|
if not dbengine:
|
370
389
|
rw_engine = get_engine(settings, master_prefix + ".")
|
371
|
-
rw_engine.c2c_name = master_prefix
|
390
|
+
rw_engine.c2c_name = master_prefix # type: ignore
|
372
391
|
|
373
392
|
# Setup a slave DB connection and add a tween to use it.
|
374
393
|
if slave_prefix and settings[master_prefix + ".url"] != settings.get(slave_prefix + ".url"):
|
375
394
|
LOG.info("Using a slave DB for reading %s", master_prefix)
|
376
395
|
ro_engine = get_engine(config.get_settings(), slave_prefix + ".")
|
377
|
-
ro_engine.c2c_name = slave_prefix
|
396
|
+
ro_engine.c2c_name = slave_prefix # type: ignore
|
378
397
|
else:
|
379
398
|
ro_engine = rw_engine
|
380
399
|
else:
|
@@ -390,6 +409,8 @@ def init(
|
|
390
409
|
if dbsession is None:
|
391
410
|
# request.tm is the transaction manager used by pyramid_tm
|
392
411
|
dbsession = get_tm_session_pyramid(session_factory, request.tm, request=request)
|
412
|
+
assert dbsession is not None
|
413
|
+
assert isinstance(dbsession, sqlalchemy.orm.Session), type(dbsession)
|
393
414
|
return dbsession
|
394
415
|
|
395
416
|
config.add_request_method(dbsession, reify=True)
|
c2cwsgiutils/debug/_listeners.py
CHANGED
@@ -3,7 +3,8 @@ import sys
|
|
3
3
|
import threading
|
4
4
|
import time
|
5
5
|
import traceback
|
6
|
-
from
|
6
|
+
from collections.abc import Mapping
|
7
|
+
from typing import Any, Optional, cast
|
7
8
|
|
8
9
|
import objgraph
|
9
10
|
|
@@ -13,7 +14,7 @@ from c2cwsgiutils.debug.utils import get_size
|
|
13
14
|
FILES_FIELDS = {"__name__", "__doc__", "__package__", "__loader__", "__spec__", "__file__"}
|
14
15
|
|
15
16
|
|
16
|
-
def _dump_stacks_impl() ->
|
17
|
+
def _dump_stacks_impl() -> dict[str, Any]:
|
17
18
|
id2name = {th.ident: th.name for th in threading.enumerate()}
|
18
19
|
threads = {}
|
19
20
|
for thread_id, stack in sys._current_frames().items(): # pylint: disable=W0212
|
@@ -47,10 +48,10 @@ def _dump_memory_impl(
|
|
47
48
|
|
48
49
|
if analyze_type:
|
49
50
|
# timeout after one minute, must be set to a bit less that the timeout of the broadcast in _views.py
|
50
|
-
timeout = time.
|
51
|
+
timeout = time.perf_counter() + 60
|
51
52
|
|
52
|
-
mod_counts:
|
53
|
-
biggest_objects:
|
53
|
+
mod_counts: dict[str, int] = {}
|
54
|
+
biggest_objects: list[tuple[float, Any]] = []
|
54
55
|
result[analyze_type] = {}
|
55
56
|
for obj in objgraph.by_type(analyze_type):
|
56
57
|
if analyze_type == "builtins.function":
|
@@ -80,12 +81,12 @@ def _dump_memory_impl(
|
|
80
81
|
biggest_objects.sort(key=lambda x: x[0])
|
81
82
|
if len(biggest_objects) > limit:
|
82
83
|
biggest_objects = biggest_objects[-limit:]
|
83
|
-
if time.
|
84
|
+
if time.perf_counter() > timeout:
|
84
85
|
result[analyze_type]["timeout"] = True
|
85
86
|
break
|
86
87
|
if analyze_type == "builtins.function":
|
87
88
|
result[analyze_type]["modules"] = [
|
88
|
-
|
89
|
+
{"module": i[0], "nb_func": i[1]}
|
89
90
|
for i in sorted(mod_counts.items(), key=lambda x: -x[1])[:limit]
|
90
91
|
]
|
91
92
|
elif analyze_type == "linecache":
|
@@ -93,13 +94,13 @@ def _dump_memory_impl(
|
|
93
94
|
|
94
95
|
cache = linecache.cache
|
95
96
|
result[analyze_type]["biggest_objects"] = sorted(
|
96
|
-
(
|
97
|
+
({"filename": k, "size_kb": get_size(v)} for k, v in cache.items()),
|
97
98
|
key=lambda i: -(cast(int, i["size_kb"])),
|
98
99
|
)
|
99
100
|
else:
|
100
101
|
biggest_objects.reverse()
|
101
102
|
result[analyze_type]["biggest_objects"] = [
|
102
|
-
|
103
|
+
{"size_kb": i[0], "repr": repr(i[1])} for i in biggest_objects
|
103
104
|
]
|
104
105
|
return result
|
105
106
|
|
c2cwsgiutils/debug/_views.py
CHANGED
@@ -2,9 +2,10 @@ import gc
|
|
2
2
|
import logging
|
3
3
|
import re
|
4
4
|
import time
|
5
|
+
from collections.abc import Mapping
|
5
6
|
from datetime import datetime
|
6
7
|
from io import StringIO
|
7
|
-
from typing import Any, Callable,
|
8
|
+
from typing import Any, Callable, cast
|
8
9
|
|
9
10
|
import objgraph
|
10
11
|
import pyramid.config
|
@@ -19,9 +20,9 @@ LOG = logging.getLogger(__name__)
|
|
19
20
|
SPACE_RE = re.compile(r" +")
|
20
21
|
|
21
22
|
|
22
|
-
def _beautify_stacks(source:
|
23
|
+
def _beautify_stacks(source: list[Mapping[str, Any]]) -> list[Mapping[str, Any]]:
|
23
24
|
"""Group the identical stacks together along with a list of threads sporting them."""
|
24
|
-
results:
|
25
|
+
results: list[Mapping[str, Any]] = []
|
25
26
|
for host_stacks in source:
|
26
27
|
host_id = f"{host_stacks['hostname']}/{host_stacks['pid']:d}"
|
27
28
|
for thread, frames in host_stacks["threads"].items():
|
@@ -35,14 +36,14 @@ def _beautify_stacks(source: List[Mapping[str, Any]]) -> List[Mapping[str, Any]]
|
|
35
36
|
return results
|
36
37
|
|
37
38
|
|
38
|
-
def _dump_stacks(request: pyramid.request.Request) ->
|
39
|
+
def _dump_stacks(request: pyramid.request.Request) -> list[Mapping[str, Any]]:
|
39
40
|
auth.auth_view(request)
|
40
41
|
result = broadcast.broadcast("c2c_dump_stacks", expect_answers=True)
|
41
42
|
assert result is not None
|
42
43
|
return _beautify_stacks(result)
|
43
44
|
|
44
45
|
|
45
|
-
def _dump_memory(request: pyramid.request.Request) ->
|
46
|
+
def _dump_memory(request: pyramid.request.Request) -> list[Mapping[str, Any]]:
|
46
47
|
auth.auth_view(request)
|
47
48
|
limit = int(request.params.get("limit", "30"))
|
48
49
|
analyze_type = request.params.get("analyze_type")
|
@@ -57,7 +58,7 @@ def _dump_memory(request: pyramid.request.Request) -> List[Mapping[str, Any]]:
|
|
57
58
|
return result
|
58
59
|
|
59
60
|
|
60
|
-
def _dump_memory_diff(request: pyramid.request.Request) ->
|
61
|
+
def _dump_memory_diff(request: pyramid.request.Request) -> list[Any]:
|
61
62
|
auth.auth_view(request)
|
62
63
|
limit = int(request.params.get("limit", "30"))
|
63
64
|
if "path" in request.matchdict:
|
@@ -72,7 +73,7 @@ def _dump_memory_diff(request: pyramid.request.Request) -> List[Any]:
|
|
72
73
|
if len(split_path) > 1:
|
73
74
|
sub_request.query_string = split_path[1]
|
74
75
|
|
75
|
-
#
|
76
|
+
# warm-up run
|
76
77
|
try:
|
77
78
|
if "no_warmup" not in request.params:
|
78
79
|
request.invoke_subrequest(sub_request)
|
@@ -81,7 +82,7 @@ def _dump_memory_diff(request: pyramid.request.Request) -> List[Any]:
|
|
81
82
|
|
82
83
|
LOG.debug("checking memory growth for %s", path)
|
83
84
|
|
84
|
-
peak_stats:
|
85
|
+
peak_stats: dict[Any, Any] = {}
|
85
86
|
for i in range(3):
|
86
87
|
gc.collect(i)
|
87
88
|
|
@@ -158,7 +159,7 @@ def _add_view(
|
|
158
159
|
config.add_view(view, route_name="c2c_debug_" + name, renderer="fast_json", http_cache=0)
|
159
160
|
|
160
161
|
|
161
|
-
def _dump_memory_maps(request: pyramid.request.Request) ->
|
162
|
+
def _dump_memory_maps(request: pyramid.request.Request) -> list[dict[str, Any]]:
|
162
163
|
auth.auth_view(request)
|
163
164
|
return sorted(dump_memory_maps(), key=lambda i: cast(int, -i.get("pss_kb", 0)))
|
164
165
|
|
@@ -168,13 +169,13 @@ def _show_refs(request: pyramid.request.Request) -> pyramid.response.Response:
|
|
168
169
|
for generation in range(3):
|
169
170
|
gc.collect(generation)
|
170
171
|
|
171
|
-
objs:
|
172
|
+
objs: list[Any] = []
|
172
173
|
if "analyze_type" in request.params:
|
173
174
|
objs = objgraph.by_type(request.params["analyze_type"])
|
174
175
|
elif "analyze_id" in request.params:
|
175
176
|
objs = [objgraph.by(int(request.params["analyze_id"]))]
|
176
177
|
|
177
|
-
args:
|
178
|
+
args: dict[str, Any] = {
|
178
179
|
"refcounts": True,
|
179
180
|
}
|
180
181
|
if request.params.get("max_depth", "") != "":
|
c2cwsgiutils/debug/utils.py
CHANGED
@@ -5,7 +5,7 @@ import re
|
|
5
5
|
import sys
|
6
6
|
from collections import defaultdict
|
7
7
|
from types import FunctionType, ModuleType
|
8
|
-
from typing import Any
|
8
|
+
from typing import Any
|
9
9
|
|
10
10
|
# 7ff7d33bd000-7ff7d33be000 r--p 00000000 00:65 49 /usr/lib/toto.so
|
11
11
|
SMAPS_LOCATION_RE = re.compile(r"^[0-9a-f]+-[0-9a-f]+ +.... +[0-9a-f]+ +[^ ]+ +\d+ +(.*)$")
|
@@ -21,7 +21,7 @@ def get_size(obj: Any) -> int:
|
|
21
21
|
"""Get the sum size of object & members."""
|
22
22
|
if isinstance(obj, BLACKLIST):
|
23
23
|
return 0
|
24
|
-
seen_ids:
|
24
|
+
seen_ids: set[int] = set()
|
25
25
|
size = 0
|
26
26
|
objects = [obj]
|
27
27
|
while objects:
|
@@ -35,14 +35,14 @@ def get_size(obj: Any) -> int:
|
|
35
35
|
return size
|
36
36
|
|
37
37
|
|
38
|
-
def dump_memory_maps(pid: str = "self") ->
|
38
|
+
def dump_memory_maps(pid: str = "self") -> list[dict[str, Any]]:
|
39
39
|
"""Get the Linux memory maps."""
|
40
40
|
filename = os.path.join("/proc", pid, "smaps")
|
41
41
|
if not os.path.exists(filename):
|
42
42
|
return []
|
43
43
|
with open(filename, encoding="utf-8") as input_:
|
44
|
-
cur_dict:
|
45
|
-
sizes:
|
44
|
+
cur_dict: dict[str, int] = defaultdict(int)
|
45
|
+
sizes: dict[str, Any] = {}
|
46
46
|
for line in input_:
|
47
47
|
line = line.rstrip("\n")
|
48
48
|
matcher = SMAPS_LOCATION_RE.match(line)
|
c2cwsgiutils/errors.py
CHANGED
@@ -60,7 +60,7 @@ def _do_error(
|
|
60
60
|
request.method,
|
61
61
|
request.url,
|
62
62
|
status,
|
63
|
-
extra={"
|
63
|
+
extra={"referrer": request.referrer},
|
64
64
|
exc_info=exception,
|
65
65
|
)
|
66
66
|
|
@@ -88,7 +88,7 @@ def _http_error(exception: HTTPException, request: pyramid.request.Request) -> A
|
|
88
88
|
request.url,
|
89
89
|
exception.status_code,
|
90
90
|
str(exception),
|
91
|
-
extra={"
|
91
|
+
extra={"referrer": request.referrer},
|
92
92
|
)
|
93
93
|
request.response.headers.update(exception.headers) # forward headers
|
94
94
|
_add_cors(request)
|
@@ -107,10 +107,11 @@ def _include_dev_details(request: pyramid.request.Request) -> bool:
|
|
107
107
|
def _integrity_error(
|
108
108
|
exception: sqlalchemy.exc.StatementError, request: pyramid.request.Request
|
109
109
|
) -> pyramid.response.Response:
|
110
|
-
def reduce_info_sent(e:
|
111
|
-
|
112
|
-
|
113
|
-
|
110
|
+
def reduce_info_sent(e: Exception) -> None:
|
111
|
+
if isinstance(e, sqlalchemy.exc.StatementError):
|
112
|
+
# remove details (SQL statement and links to SQLAlchemy) from the error
|
113
|
+
e.statement = None
|
114
|
+
e.code = None
|
114
115
|
|
115
116
|
return _do_error(request, 400, exception, reduce_info_sent=reduce_info_sent)
|
116
117
|
|