c2cwsgiutils 6.2.0.dev25__tar.gz → 6.2.0.dev29__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/PKG-INFO +2 -1
  2. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/debug/_listeners.py +1 -6
  3. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/debug/_views.py +22 -3
  4. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/index.py +27 -4
  5. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/templates/index.html.mako +4 -1
  6. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/pyproject.toml +4 -2
  7. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/LICENSE +0 -0
  8. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/README.md +0 -0
  9. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/__init__.py +0 -0
  10. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/acceptance/__init__.py +0 -0
  11. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/acceptance/connection.py +0 -0
  12. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/acceptance/image.py +0 -0
  13. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/acceptance/package-lock.json +0 -0
  14. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/acceptance/package.json +0 -0
  15. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/acceptance/print.py +0 -0
  16. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/acceptance/screenshot.js +0 -0
  17. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/acceptance/utils.py +0 -0
  18. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/auth.py +0 -0
  19. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/broadcast/__init__.py +0 -0
  20. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/broadcast/interface.py +0 -0
  21. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/broadcast/local.py +0 -0
  22. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/broadcast/redis.py +0 -0
  23. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/broadcast/utils.py +0 -0
  24. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/client_info.py +0 -0
  25. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/config_utils.py +0 -0
  26. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/coverage_setup.py +0 -0
  27. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/db.py +0 -0
  28. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/db_maintenance_view.py +0 -0
  29. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/debug/__init__.py +0 -0
  30. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/debug/utils.py +0 -0
  31. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/errors.py +0 -0
  32. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/health_check.py +0 -0
  33. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/loader.py +0 -0
  34. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/logging_view.py +0 -0
  35. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/models_graph.py +0 -0
  36. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/pretty_json.py +0 -0
  37. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/profiler.py +0 -0
  38. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/prometheus.py +0 -0
  39. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/py.typed +0 -0
  40. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/pyramid.py +0 -0
  41. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/pyramid_logging.py +0 -0
  42. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/redis_stats.py +0 -0
  43. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/redis_utils.py +0 -0
  44. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/request_tracking/__init__.py +0 -0
  45. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/request_tracking/_sql.py +0 -0
  46. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/scripts/__init__.py +0 -0
  47. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/scripts/genversion.py +0 -0
  48. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/scripts/stats_db.py +0 -0
  49. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/scripts/test_print.py +0 -0
  50. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/sentry.py +0 -0
  51. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/services.py +0 -0
  52. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/setup_process.py +0 -0
  53. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/sql_profiler/__init__.py +0 -0
  54. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/sql_profiler/_impl.py +0 -0
  55. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/sqlalchemylogger/README.md +0 -0
  56. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/sqlalchemylogger/__init__.py +0 -0
  57. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/sqlalchemylogger/_filters.py +0 -0
  58. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/sqlalchemylogger/_models.py +0 -0
  59. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/sqlalchemylogger/examples/example.py +0 -0
  60. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/sqlalchemylogger/handlers.py +0 -0
  61. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/static/favicon-16x16.png +0 -0
  62. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/static/favicon-32x32.png +0 -0
  63. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/stats_pyramid/__init__.py +0 -0
  64. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/stats_pyramid/_db_spy.py +0 -0
  65. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/stats_pyramid/_pyramid_spy.py +0 -0
  66. {c2cwsgiutils-6.2.0.dev25 → c2cwsgiutils-6.2.0.dev29}/c2cwsgiutils/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: c2cwsgiutils
3
- Version: 6.2.0.dev25
3
+ Version: 6.2.0.dev29
4
4
  Summary: Common utilities for Camptocamp WSGI applications
5
5
  Home-page: https://github.com/camptocamp/c2cwsgiutils
6
6
  License: BSD-2-Clause
@@ -44,6 +44,7 @@ Requires-Dist: gunicorn ; extra == "standard" or extra == "webserver" or extra =
44
44
  Requires-Dist: lxml ; extra == "tests" or extra == "all"
45
45
  Requires-Dist: objgraph ; extra == "debug" or extra == "all"
46
46
  Requires-Dist: prometheus-client ; extra == "standard" or extra == "webserver" or extra == "all"
47
+ Requires-Dist: psutil ; extra == "debug" or extra == "all"
47
48
  Requires-Dist: psycopg2 ; extra == "standard" or extra == "webserver" or extra == "all"
48
49
  Requires-Dist: pyjwt ; extra == "standard" or extra == "oauth2" or extra == "all"
49
50
  Requires-Dist: pyramid ; extra == "standard" or extra == "webserver" or extra == "all"
@@ -68,12 +68,7 @@ def _dump_memory_impl(
68
68
  and not (FILES_FIELDS - set(obj["globals"].keys()))
69
69
  ):
70
70
  python_internal = True
71
- if (
72
- python_internal
73
- and not python_internals_map
74
- or not python_internal
75
- and python_internals_map
76
- ):
71
+ if python_internal != python_internals_map:
77
72
  continue
78
73
  size = get_size(obj) / 1024
79
74
  if len(biggest_objects) < limit or size > biggest_objects[0][0]:
@@ -1,5 +1,6 @@
1
1
  import gc
2
2
  import logging
3
+ import os
3
4
  import re
4
5
  import time
5
6
  from collections.abc import Mapping
@@ -8,6 +9,7 @@ from io import StringIO
8
9
  from typing import Any, Callable, cast
9
10
 
10
11
  import objgraph
12
+ import psutil
11
13
  import pyramid.config
12
14
  import pyramid.request
13
15
  import pyramid.response
@@ -47,7 +49,7 @@ def _dump_memory(request: pyramid.request.Request) -> list[Mapping[str, Any]]:
47
49
  auth.auth_view(request)
48
50
  limit = int(request.params.get("limit", "30"))
49
51
  analyze_type = request.params.get("analyze_type")
50
- python_internals_map = request.params.get("python_internals_map", "0").lower() in ("", "1", "true", "on")
52
+ python_internals_map = request.params.get("python_internals_map", "0").lower() in ("1", "true", "on")
51
53
  result = broadcast.broadcast(
52
54
  "c2c_dump_memory",
53
55
  params={"limit": limit, "analyze_type": analyze_type, "python_internals_map": python_internals_map},
@@ -75,7 +77,7 @@ def _dump_memory_diff(request: pyramid.request.Request) -> list[Any]:
75
77
 
76
78
  # warm-up run
77
79
  try:
78
- if "no_warmup" not in request.params:
80
+ if request.params.get("no_warmup", "0").lower() in ("1", "true", "on"):
79
81
  request.invoke_subrequest(sub_request)
80
82
  except Exception: # nosec # pylint: disable=broad-except
81
83
  pass
@@ -87,6 +89,9 @@ def _dump_memory_diff(request: pyramid.request.Request) -> list[Any]:
87
89
  gc.collect(i)
88
90
 
89
91
  objgraph.growth(limit=limit, peak_stats=peak_stats, shortnames=False)
92
+ process = psutil.Process(os.getpid())
93
+ mem_before = process.memory_info()
94
+ start_time = time.time()
90
95
 
91
96
  response = None
92
97
  try:
@@ -96,12 +101,26 @@ def _dump_memory_diff(request: pyramid.request.Request) -> list[Any]:
96
101
  except HTTPException as ex:
97
102
  _LOG.debug("response was %s", str(ex))
98
103
 
104
+ elapsed_time = time.time() - start_time
99
105
  del response
100
106
 
101
107
  for i in range(3):
102
108
  gc.collect(i)
103
109
 
104
- return objgraph.growth(limit=limit, peak_stats=peak_stats, shortnames=False) # type: ignore
110
+ mem_after = process.memory_info()
111
+ return {
112
+ "memory.growth": {
113
+ "rss_kb": (mem_after.rss - mem_before.rss) / 1024,
114
+ "vms_kb": (mem_after.vms - mem_before.vms) / 1024,
115
+ "shared_kb": (mem_after.shared - mem_before.shared) / 1024,
116
+ "text_kb": (mem_after.text - mem_before.text) / 1024,
117
+ "lib_kb": (mem_after.lib - mem_before.lib) / 1024,
118
+ "data_kb": (mem_after.data - mem_before.data) / 1024,
119
+ "dirty_kb": (mem_after.dirty - mem_before.dirty) / 1024,
120
+ },
121
+ "elapsed_time": elapsed_time,
122
+ "objgraph.growth": objgraph.growth(limit=limit, peak_stats=peak_stats, shortnames=False), # type: ignore
123
+ }
105
124
 
106
125
 
107
126
  def _sleep(request: pyramid.request.Request) -> pyramid.response.Response:
@@ -122,8 +122,20 @@ def input_(
122
122
  if type_ is None:
123
123
  if isinstance(value, int):
124
124
  type_ = "number"
125
+ elif isinstance(value, bool):
126
+ type_ = "checkbox"
125
127
  else:
126
128
  type_ = "text"
129
+ if type_ == "checkbox":
130
+ checked = " checked" if value else ""
131
+ return f"""
132
+ <div class="form-check">
133
+ <input class="form-check-input" type="checkbox" name="{name}" value="true" id="{id_}"{checked}>
134
+ <label class="form-check-label" for="{id_}">
135
+ {label}
136
+ </label>
137
+ </div>"""
138
+
127
139
  result = ""
128
140
  if label is not None:
129
141
  result += f'<div class="row mb-3"><label class="col-sm-2 col-form-label" for="{id_}">{label}</label>'
@@ -201,7 +213,7 @@ def _index(request: pyramid.request.Request) -> dict[str, str]:
201
213
  def _versions(request: pyramid.request.Request) -> str:
202
214
  versions_url = _url(request, "c2c_versions")
203
215
  if versions_url:
204
- return section("Versions", paragraph(link(versions_url, "Get")), sep=False)
216
+ return section("Versions " + link(versions_url, "Get"), sep=False)
205
217
  else:
206
218
  return ""
207
219
 
@@ -280,6 +292,7 @@ def _logging(request: pyramid.request.Request) -> str:
280
292
  def _debug(request: pyramid.request.Request) -> str:
281
293
  dump_memory_url = _url(request, "c2c_debug_memory")
282
294
  if dump_memory_url:
295
+ as_dot = 'as <a href="https://graphviz.org/">dot diagram</a>, can be open with <a href="https://pypi.org/project/xdot/">xdot</a>'
283
296
  return section(
284
297
  " ".join(
285
298
  [
@@ -289,32 +302,42 @@ def _debug(request: pyramid.request.Request) -> str:
289
302
  link(_url(request, "c2c_debug_memory_maps"), "Mapped memory"),
290
303
  ]
291
304
  ),
305
+ '<h2>Memory usage<span style="font-size: 0.5em;">, with <a href="https://mg.pov.lt/objgraph/">objgraph</a></span></h2>',
306
+ "<p>Runs the garbage collector and dumps the memory usage as JSON.</p>",
292
307
  form(
293
308
  dump_memory_url,
294
309
  input_("limit", value=30),
295
310
  input_("analyze_type"),
311
+ input_("python_internals_map", type_="checkbox"),
296
312
  button("Dump memory usage"),
297
313
  ),
314
+ f"<p>Runs the garbage collector and dumps the memory refs {as_dot}.</p>",
298
315
  form(
299
316
  _url(request, "c2c_debug_show_refs"),
300
317
  input_("analyze_type", value="gunicorn.app.wsgiapp.WSGIApplication"),
301
- input_("max_depth", value=3),
302
- input_("too_many", value=10),
318
+ input_("analyze_id", type_="number"),
319
+ input_("max_depth", type_="number", value=3),
320
+ input_("too_many", type_="number", value=10),
303
321
  input_("min_size_kb", type_="number"),
304
322
  button("Object refs"),
305
323
  ),
324
+ "<p>Runs the garbage collector, query the path, runs the garbage collector again, get the memory diff as JSON.</p>",
306
325
  form(
307
326
  _url(request, "c2c_debug_memory_diff"),
308
327
  input_("path"),
309
328
  input_("limit", value=30),
329
+ input_("no_warmup", type_="checkbox"),
310
330
  button("Memory diff"),
311
331
  ),
332
+ "<h2>Sleep</h2>",
312
333
  form(
313
334
  _url(request, "c2c_debug_sleep"),
314
335
  input_("time", value=1),
315
336
  button("Sleep"),
316
337
  ),
317
- form(_url(request, "c2c_debug_time"), button("Time")),
338
+ "<h2>Server times</h2>",
339
+ form(_url(request, "c2c_debug_time"), button("Get")),
340
+ "<h2>HTTP error</h2>",
318
341
  form(
319
342
  _url(request, "c2c_debug_error"),
320
343
  input_("status", value=500),
@@ -24,7 +24,7 @@
24
24
  crossorigin="anonymous"
25
25
  referrerpolicy="no-referrer"
26
26
  />
27
- <title>c2cwsgiutils tools</title>
27
+ <title>C2C WSGI Utils tools</title>
28
28
  <style>
29
29
  body {
30
30
  margin-top: 0.5rem;
@@ -32,6 +32,9 @@
32
32
  button, p {
33
33
  margin-bottom: 0.5rem;
34
34
  }
35
+ .row > h2 {
36
+ margin-top: 1rem;
37
+ }
35
38
  </style>
36
39
  </head>
37
40
  <body>
@@ -16,7 +16,7 @@ strict = true
16
16
 
17
17
  [tool.poetry]
18
18
  name = "c2cwsgiutils"
19
- version = "6.2.0.dev25"
19
+ version = "6.2.0.dev29"
20
20
  description = "Common utilities for Camptocamp WSGI applications"
21
21
  readme = "README.md"
22
22
  authors = ["Camptocamp <info@camptocamp.com>"]
@@ -92,6 +92,7 @@ waitress = { version = "3.0.1", optional = true }
92
92
  scikit-image = { version = "0.24.0", optional = true }
93
93
  prometheus-client = { version = "0.21.0", optional = true}
94
94
  pyramid_mako = { version = "1.1.0", optional = true}
95
+ psutil = { version = "6.1.0", optional = true}
95
96
 
96
97
  [tool.poetry.extras]
97
98
  standard = [
@@ -118,7 +119,7 @@ standard = [
118
119
  "pyramid_mako",
119
120
  ]
120
121
  alembic = ["alembic"]
121
- debug = ["objgraph"]
122
+ debug = ["objgraph", "psutil"]
122
123
  oauth2 = ["pyjwt", "requests-oauthlib"]
123
124
  sentry = ["sentry-sdk"]
124
125
  dev = ["waitress"]
@@ -141,6 +142,7 @@ all = [
141
142
  "alembic",
142
143
  # debug
143
144
  "objgraph",
145
+ "psutil",
144
146
  # oauth2
145
147
  "pyjwt",
146
148
  "requests-oauthlib",