c2cwsgiutils 6.2.0.dev36__tar.gz → 6.2.0.dev41__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.
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/PKG-INFO +73 -12
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/README.md +58 -0
- c2cwsgiutils-6.2.0.dev41/c2cwsgiutils/loader.py +39 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/prometheus.py +56 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/setup_process.py +2 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/pyproject.toml +24 -5
- c2cwsgiutils-6.2.0.dev36/c2cwsgiutils/loader.py +0 -21
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/LICENSE +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/__init__.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/acceptance/__init__.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/acceptance/connection.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/acceptance/image.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/acceptance/package-lock.json +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/acceptance/package.json +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/acceptance/print.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/acceptance/screenshot.js +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/acceptance/utils.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/auth.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/broadcast/__init__.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/broadcast/interface.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/broadcast/local.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/broadcast/redis.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/broadcast/utils.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/client_info.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/config_utils.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/coverage_setup.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/db.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/db_maintenance_view.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/debug/__init__.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/debug/_listeners.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/debug/_views.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/debug/utils.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/errors.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/health_check.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/index.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/logging_view.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/models_graph.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/pretty_json.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/profiler.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/py.typed +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/pyramid.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/pyramid_logging.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/redis_stats.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/redis_utils.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/request_tracking/__init__.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/request_tracking/_sql.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/scripts/__init__.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/scripts/genversion.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/scripts/stats_db.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/scripts/test_print.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/sentry.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/services.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/sql_profiler/__init__.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/sql_profiler/_impl.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/sqlalchemylogger/README.md +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/sqlalchemylogger/__init__.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/sqlalchemylogger/_filters.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/sqlalchemylogger/_models.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/sqlalchemylogger/examples/example.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/sqlalchemylogger/handlers.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/static/favicon-16x16.png +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/static/favicon-32x32.png +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/stats_pyramid/__init__.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/stats_pyramid/_db_spy.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/stats_pyramid/_pyramid_spy.py +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/templates/index.html.mako +0 -0
- {c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/version.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: c2cwsgiutils
|
3
|
-
Version: 6.2.0.
|
3
|
+
Version: 6.2.0.dev41
|
4
4
|
Summary: Common utilities for Camptocamp WSGI applications
|
5
5
|
Home-page: https://github.com/camptocamp/c2cwsgiutils
|
6
6
|
License: BSD-2-Clause
|
@@ -33,23 +33,26 @@ Provides-Extra: sentry
|
|
33
33
|
Provides-Extra: standard
|
34
34
|
Provides-Extra: test-images
|
35
35
|
Provides-Extra: tests
|
36
|
+
Provides-Extra: waitress
|
36
37
|
Provides-Extra: webserver
|
37
|
-
Requires-Dist:
|
38
|
-
Requires-Dist: SQLAlchemy
|
38
|
+
Requires-Dist: Paste ; extra == "standard" or extra == "waitress" or extra == "all"
|
39
|
+
Requires-Dist: SQLAlchemy ; extra == "standard" or extra == "webserver" or extra == "waitress" or extra == "all"
|
40
|
+
Requires-Dist: SQLAlchemy-Utils ; extra == "standard" or extra == "webserver" or extra == "waitress" or extra == "all"
|
39
41
|
Requires-Dist: alembic ; extra == "standard" or extra == "alembic" or extra == "all"
|
40
42
|
Requires-Dist: boltons ; extra == "tests" or extra == "all"
|
41
43
|
Requires-Dist: cee_syslog_handler
|
42
|
-
Requires-Dist: cornice ; extra == "standard" or extra == "webserver" or extra == "all"
|
44
|
+
Requires-Dist: cornice ; extra == "standard" or extra == "webserver" or extra == "waitress" or extra == "all"
|
45
|
+
Requires-Dist: coverage ; extra == "debug" or extra == "all"
|
43
46
|
Requires-Dist: gunicorn ; extra == "standard" or extra == "webserver" or extra == "all"
|
44
47
|
Requires-Dist: lxml ; extra == "tests" or extra == "all"
|
45
48
|
Requires-Dist: objgraph ; extra == "debug" or extra == "all"
|
46
|
-
Requires-Dist: prometheus-client ; extra == "standard" or extra == "webserver" or extra == "all"
|
49
|
+
Requires-Dist: prometheus-client ; extra == "standard" or extra == "webserver" or extra == "waitress" or extra == "all"
|
47
50
|
Requires-Dist: psutil ; extra == "debug" or extra == "all"
|
48
|
-
Requires-Dist: psycopg2 ; extra == "standard" or extra == "webserver" or extra == "all"
|
51
|
+
Requires-Dist: psycopg2 ; extra == "standard" or extra == "webserver" or extra == "waitress" or extra == "all"
|
49
52
|
Requires-Dist: pyjwt ; extra == "standard" or extra == "oauth2" or extra == "all"
|
50
|
-
Requires-Dist: pyramid ; extra == "standard" or extra == "webserver" or extra == "all"
|
51
|
-
Requires-Dist: pyramid-tm ; extra == "standard" or extra == "webserver" or extra == "all"
|
52
|
-
Requires-Dist: pyramid_mako ; extra == "standard" or extra == "webserver" or extra == "all"
|
53
|
+
Requires-Dist: pyramid ; extra == "standard" or extra == "webserver" or extra == "waitress" or extra == "all"
|
54
|
+
Requires-Dist: pyramid-tm ; extra == "standard" or extra == "webserver" or extra == "waitress" or extra == "all"
|
55
|
+
Requires-Dist: pyramid_mako ; extra == "standard" or extra == "webserver" or extra == "waitress" or extra == "all"
|
53
56
|
Requires-Dist: pyyaml
|
54
57
|
Requires-Dist: redis ; extra == "standard" or extra == "broadcast" or extra == "all"
|
55
58
|
Requires-Dist: requests
|
@@ -57,9 +60,9 @@ Requires-Dist: requests-oauthlib ; extra == "standard" or extra == "oauth2" or e
|
|
57
60
|
Requires-Dist: scikit-image ; extra == "test-images"
|
58
61
|
Requires-Dist: sentry-sdk ; extra == "standard" or extra == "sentry" or extra == "all"
|
59
62
|
Requires-Dist: ujson
|
60
|
-
Requires-Dist: waitress ; extra == "dev" or extra == "all"
|
61
|
-
Requires-Dist: zope.interface ; extra == "standard" or extra == "webserver" or extra == "all"
|
62
|
-
Requires-Dist: zope.sqlalchemy ; extra == "standard" or extra == "webserver" or extra == "all"
|
63
|
+
Requires-Dist: waitress ; extra == "standard" or extra == "dev" or extra == "waitress" or extra == "all"
|
64
|
+
Requires-Dist: zope.interface ; extra == "standard" or extra == "webserver" or extra == "waitress" or extra == "all"
|
65
|
+
Requires-Dist: zope.sqlalchemy ; extra == "standard" or extra == "webserver" or extra == "waitress" or extra == "all"
|
63
66
|
Project-URL: Repository, https://github.com/camptocamp/c2cwsgiutils
|
64
67
|
Description-Content-Type: text/markdown
|
65
68
|
|
@@ -673,6 +676,64 @@ def hello_get(request):
|
|
673
676
|
return {'hello': True}
|
674
677
|
```
|
675
678
|
|
679
|
+
## Waitress
|
680
|
+
|
681
|
+
In production mode we usually use Gunicorn but we can also use Waitress.
|
682
|
+
|
683
|
+
The advantage to use Waitress is that it creates only one process, that makes it easier to manage especially on Kubernetes:
|
684
|
+
|
685
|
+
- The memory is more stable.
|
686
|
+
- The OOM killer will restart the container.
|
687
|
+
- Prometheus didn't request trick to aggregate the metrics.
|
688
|
+
|
689
|
+
Then to migrate from Gunicorn to Waitress you should do:
|
690
|
+
|
691
|
+
Add call to `c2cwsgiutils.prometheus.start_single_process()` on your application main function.
|
692
|
+
|
693
|
+
Changes to do in your docker file:
|
694
|
+
|
695
|
+
```diff
|
696
|
+
|
697
|
+
ENV \
|
698
|
+
- GUNICORN_LOG_LEVEL=WARNING \
|
699
|
+
+ WAITRESS_LOG_LEVEL=WARNING \
|
700
|
+
+ WAITRESS_THREADS=10 \
|
701
|
+
|
702
|
+
-RUN mkdir -p /prometheus-metrics \
|
703
|
+
- && chmod a+rwx /prometheus-metrics
|
704
|
+
-ENV PROMETHEUS_MULTIPROC_DIR=/prometheus-metrics
|
705
|
+
|
706
|
+
|
707
|
+
-CMD ["/venv/bin/gunicorn", "--paste=/app/production.ini"]
|
708
|
+
+CMD ["/venv/bin/pserve", "c2c:///app/production.ini"]
|
709
|
+
```
|
710
|
+
|
711
|
+
Remove the no more needed file `gunicorn.conf.py`.
|
712
|
+
|
713
|
+
Update the `production.ini` file:
|
714
|
+
|
715
|
+
```diff
|
716
|
+
|
717
|
+
-# this file should be used by gunicorn.
|
718
|
+
|
719
|
+
[server:main]
|
720
|
+
+threads = %(WAITRESS_THREADS)s
|
721
|
+
+trusted_proxy = True
|
722
|
+
+clear_untrusted_proxy_headers = False
|
723
|
+
|
724
|
+
[loggers]
|
725
|
+
-keys = root, gunicorn, sqlalchemy, c2cwsgiutils, c2cwsgiutils_app
|
726
|
+
+keys = root, waitress, sqlalchemy, c2cwsgiutils, c2cwsgiutils_app
|
727
|
+
|
728
|
+
-[logger_gunicorn]
|
729
|
+
-level = %(GUNICORN_LOG_LEVEL)s
|
730
|
+
+[logger_waitress]
|
731
|
+
+level = %(WAITRESS_LOG_LEVEL)s
|
732
|
+
handlers =
|
733
|
+
-qualname = gunicorn.error
|
734
|
+
+qualname = waitress
|
735
|
+
```
|
736
|
+
|
676
737
|
# Exception handling
|
677
738
|
|
678
739
|
c2cwsgiutils can install exception handling views that will catch any exception raised by the
|
@@ -608,6 +608,64 @@ def hello_get(request):
|
|
608
608
|
return {'hello': True}
|
609
609
|
```
|
610
610
|
|
611
|
+
## Waitress
|
612
|
+
|
613
|
+
In production mode we usually use Gunicorn but we can also use Waitress.
|
614
|
+
|
615
|
+
The advantage to use Waitress is that it creates only one process, that makes it easier to manage especially on Kubernetes:
|
616
|
+
|
617
|
+
- The memory is more stable.
|
618
|
+
- The OOM killer will restart the container.
|
619
|
+
- Prometheus didn't request trick to aggregate the metrics.
|
620
|
+
|
621
|
+
Then to migrate from Gunicorn to Waitress you should do:
|
622
|
+
|
623
|
+
Add call to `c2cwsgiutils.prometheus.start_single_process()` on your application main function.
|
624
|
+
|
625
|
+
Changes to do in your docker file:
|
626
|
+
|
627
|
+
```diff
|
628
|
+
|
629
|
+
ENV \
|
630
|
+
- GUNICORN_LOG_LEVEL=WARNING \
|
631
|
+
+ WAITRESS_LOG_LEVEL=WARNING \
|
632
|
+
+ WAITRESS_THREADS=10 \
|
633
|
+
|
634
|
+
-RUN mkdir -p /prometheus-metrics \
|
635
|
+
- && chmod a+rwx /prometheus-metrics
|
636
|
+
-ENV PROMETHEUS_MULTIPROC_DIR=/prometheus-metrics
|
637
|
+
|
638
|
+
|
639
|
+
-CMD ["/venv/bin/gunicorn", "--paste=/app/production.ini"]
|
640
|
+
+CMD ["/venv/bin/pserve", "c2c:///app/production.ini"]
|
641
|
+
```
|
642
|
+
|
643
|
+
Remove the no more needed file `gunicorn.conf.py`.
|
644
|
+
|
645
|
+
Update the `production.ini` file:
|
646
|
+
|
647
|
+
```diff
|
648
|
+
|
649
|
+
-# this file should be used by gunicorn.
|
650
|
+
|
651
|
+
[server:main]
|
652
|
+
+threads = %(WAITRESS_THREADS)s
|
653
|
+
+trusted_proxy = True
|
654
|
+
+clear_untrusted_proxy_headers = False
|
655
|
+
|
656
|
+
[loggers]
|
657
|
+
-keys = root, gunicorn, sqlalchemy, c2cwsgiutils, c2cwsgiutils_app
|
658
|
+
+keys = root, waitress, sqlalchemy, c2cwsgiutils, c2cwsgiutils_app
|
659
|
+
|
660
|
+
-[logger_gunicorn]
|
661
|
+
-level = %(GUNICORN_LOG_LEVEL)s
|
662
|
+
+[logger_waitress]
|
663
|
+
+level = %(WAITRESS_LOG_LEVEL)s
|
664
|
+
handlers =
|
665
|
+
-qualname = gunicorn.error
|
666
|
+
+qualname = waitress
|
667
|
+
```
|
668
|
+
|
611
669
|
# Exception handling
|
612
670
|
|
613
671
|
c2cwsgiutils can install exception handling views that will catch any exception raised by the
|
@@ -0,0 +1,39 @@
|
|
1
|
+
import logging.config
|
2
|
+
from typing import Optional, cast
|
3
|
+
|
4
|
+
from plaster_pastedeploy import Loader as BaseLoader
|
5
|
+
|
6
|
+
from c2cwsgiutils import get_config_defaults, get_logconfig_dict
|
7
|
+
|
8
|
+
_LOG = logging.getLogger(__name__)
|
9
|
+
|
10
|
+
|
11
|
+
class Loader(BaseLoader): # type: ignore
|
12
|
+
"""The application loader."""
|
13
|
+
|
14
|
+
def _get_defaults(self, defaults: Optional[dict[str, str]] = None) -> dict[str, str]:
|
15
|
+
d = get_config_defaults()
|
16
|
+
d.update(defaults or {})
|
17
|
+
return cast(dict[str, str], super()._get_defaults(d))
|
18
|
+
|
19
|
+
def __repr__(self) -> str:
|
20
|
+
"""Get the object representation."""
|
21
|
+
return f'c2cwsgiutils.loader.Loader(uri="{self.uri}")'
|
22
|
+
|
23
|
+
def setup_logging(self, defaults: Optional[dict[str, str]] = None) -> None:
|
24
|
+
"""
|
25
|
+
Set up logging via :func:`logging.config.fileConfig`.
|
26
|
+
|
27
|
+
Defaults are specified for the special ``__file__`` and ``here``
|
28
|
+
variables, similar to PasteDeploy config loading. Extra defaults can
|
29
|
+
optionally be specified as a dict in ``defaults``.
|
30
|
+
|
31
|
+
Arguments:
|
32
|
+
defaults: The defaults that will be used when passed to
|
33
|
+
:func:`logging.config.fileConfig`.
|
34
|
+
|
35
|
+
"""
|
36
|
+
if "loggers" in self.get_sections():
|
37
|
+
logging.config.dictConfig(get_logconfig_dict(self.uri.path))
|
38
|
+
else:
|
39
|
+
logging.basicConfig()
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
import os
|
4
4
|
import re
|
5
|
+
import resource
|
5
6
|
from collections.abc import Generator, Iterable
|
6
7
|
from typing import Any, Optional, TypedDict, cast
|
7
8
|
|
@@ -15,6 +16,12 @@ import pyramid.config
|
|
15
16
|
from c2cwsgiutils import broadcast, redis_utils
|
16
17
|
from c2cwsgiutils.debug.utils import dump_memory_maps
|
17
18
|
|
19
|
+
PSUTILS = True
|
20
|
+
try:
|
21
|
+
import psutil
|
22
|
+
except ImportError:
|
23
|
+
PSUTILS = False
|
24
|
+
|
18
25
|
_NUMBER_RE = re.compile(r"^[0-9]+$")
|
19
26
|
MULTI_PROCESS_COLLECTOR_BROADCAST_CHANNELS = [
|
20
27
|
"c2cwsgiutils_prometheus_collector_gc",
|
@@ -22,12 +29,21 @@ MULTI_PROCESS_COLLECTOR_BROADCAST_CHANNELS = [
|
|
22
29
|
]
|
23
30
|
|
24
31
|
|
32
|
+
def start_single_process() -> None:
|
33
|
+
"""Start separate HTTP server to provide the Prometheus metrics."""
|
34
|
+
if os.environ.get("C2C_PROMETHEUS_PORT") is not None:
|
35
|
+
prometheus_client.REGISTRY.register(ResourceCollector())
|
36
|
+
prometheus_client.REGISTRY.register(MemoryInfoCollector())
|
37
|
+
prometheus_client.start_http_server(int(os.environ["C2C_PROMETHEUS_PORT"]))
|
38
|
+
|
39
|
+
|
25
40
|
def start(registry: Optional[prometheus_client.CollectorRegistry] = None) -> None:
|
26
41
|
"""Start separate HTTP server to provide the Prometheus metrics."""
|
27
42
|
if os.environ.get("C2C_PROMETHEUS_PORT") is not None:
|
28
43
|
broadcast.includeme()
|
29
44
|
|
30
45
|
registry = prometheus_client.CollectorRegistry() if registry is None else registry
|
46
|
+
registry.register(ResourceCollector())
|
31
47
|
registry.register(MemoryMapCollector())
|
32
48
|
registry.register(prometheus_client.PLATFORM_COLLECTOR)
|
33
49
|
registry.register(MultiProcessCustomCollector())
|
@@ -181,3 +197,43 @@ class MemoryMapCollector(prometheus_client.registry.Collector):
|
|
181
197
|
for e in dump_memory_maps(pid):
|
182
198
|
gauge.add_metric([pid, e["name"]], e[self.memory_type + "_kb"] * 1024)
|
183
199
|
yield gauge
|
200
|
+
|
201
|
+
|
202
|
+
class ResourceCollector(prometheus_client.registry.Collector):
|
203
|
+
"""Collect the resources used by Python."""
|
204
|
+
|
205
|
+
def collect(self) -> Generator[prometheus_client.core.GaugeMetricFamily, None, None]:
|
206
|
+
"""Get the gauge from smap file."""
|
207
|
+
gauge = prometheus_client.core.GaugeMetricFamily(
|
208
|
+
build_metric_name("python_resource"),
|
209
|
+
"Python resources",
|
210
|
+
labels=["name"],
|
211
|
+
)
|
212
|
+
r = resource.getrusage(resource.RUSAGE_SELF)
|
213
|
+
for field in dir(r):
|
214
|
+
if field.startswith("ru_"):
|
215
|
+
gauge.add_metric([field[3:]], getattr(r, field))
|
216
|
+
yield gauge
|
217
|
+
|
218
|
+
|
219
|
+
class MemoryInfoCollector(prometheus_client.registry.Collector):
|
220
|
+
"""Collect the resources used by Python."""
|
221
|
+
|
222
|
+
process = psutil.Process(os.getpid())
|
223
|
+
|
224
|
+
def collect(self) -> Generator[prometheus_client.core.GaugeMetricFamily, None, None]:
|
225
|
+
"""Get the gauge from smap file."""
|
226
|
+
gauge = prometheus_client.core.GaugeMetricFamily(
|
227
|
+
build_metric_name("python_memory_info"),
|
228
|
+
"Python memory info",
|
229
|
+
labels=["name"],
|
230
|
+
)
|
231
|
+
memory_info = self.process.memory_info()
|
232
|
+
gauge.add_metric(["rss"], memory_info.rss)
|
233
|
+
gauge.add_metric(["vms"], memory_info.vms)
|
234
|
+
gauge.add_metric(["shared"], memory_info.shared)
|
235
|
+
gauge.add_metric(["text"], memory_info.text)
|
236
|
+
gauge.add_metric(["lib"], memory_info.lib)
|
237
|
+
gauge.add_metric(["data"], memory_info.data)
|
238
|
+
gauge.add_metric(["dirty"], memory_info.dirty)
|
239
|
+
yield gauge
|
@@ -5,6 +5,7 @@ Must be imported at the very beginning of the process's life, before any other m
|
|
5
5
|
"""
|
6
6
|
|
7
7
|
import argparse
|
8
|
+
import logging
|
8
9
|
import warnings
|
9
10
|
from typing import Any, Callable, Optional, TypedDict, cast
|
10
11
|
|
@@ -97,4 +98,5 @@ def bootstrap_application(
|
|
97
98
|
"""
|
98
99
|
loader = get_config_loader(config_uri)
|
99
100
|
loader.setup_logging(options)
|
101
|
+
logging.getLogger(__name__).info("Loading the application from %s", config_uri)
|
100
102
|
return cast(PyramidEnv, bootstrap(config_uri, options=options))
|
@@ -16,7 +16,7 @@ strict = true
|
|
16
16
|
|
17
17
|
[tool.poetry]
|
18
18
|
name = "c2cwsgiutils"
|
19
|
-
version = "6.2.0.
|
19
|
+
version = "6.2.0.dev41"
|
20
20
|
description = "Common utilities for Camptocamp WSGI applications"
|
21
21
|
readme = "README.md"
|
22
22
|
authors = ["Camptocamp <info@camptocamp.com>"]
|
@@ -93,6 +93,8 @@ 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
95
|
psutil = { version = "6.1.0", optional = true}
|
96
|
+
Paste = { version = "3.10.1", optional = true}
|
97
|
+
coverage = { version = "7.6.4", optional = true}
|
96
98
|
|
97
99
|
[tool.poetry.extras]
|
98
100
|
standard = [
|
@@ -117,9 +119,12 @@ standard = [
|
|
117
119
|
"zope.sqlalchemy",
|
118
120
|
"prometheus_client",
|
119
121
|
"pyramid_mako",
|
122
|
+
# waitress
|
123
|
+
"waitress",
|
124
|
+
"Paste",
|
120
125
|
]
|
121
126
|
alembic = ["alembic"]
|
122
|
-
debug = ["objgraph", "psutil"]
|
127
|
+
debug = ["objgraph", "psutil", "coverage"]
|
123
128
|
oauth2 = ["pyjwt", "requests-oauthlib"]
|
124
129
|
sentry = ["sentry-sdk"]
|
125
130
|
dev = ["waitress"]
|
@@ -136,6 +141,19 @@ webserver = [
|
|
136
141
|
"zope.sqlalchemy",
|
137
142
|
"prometheus_client",
|
138
143
|
"pyramid_mako"]
|
144
|
+
waitress = [
|
145
|
+
"waitress",
|
146
|
+
"Paste",
|
147
|
+
"cornice",
|
148
|
+
"psycopg2",
|
149
|
+
"pyramid",
|
150
|
+
"pyramid-tm",
|
151
|
+
"SQLAlchemy",
|
152
|
+
"SQLAlchemy-Utils",
|
153
|
+
"zope.interface",
|
154
|
+
"zope.sqlalchemy",
|
155
|
+
"prometheus_client",
|
156
|
+
"pyramid_mako"]
|
139
157
|
tests = ["lxml", "boltons"]
|
140
158
|
all = [
|
141
159
|
# alembic
|
@@ -143,13 +161,12 @@ all = [
|
|
143
161
|
# debug
|
144
162
|
"objgraph",
|
145
163
|
"psutil",
|
164
|
+
"coverage",
|
146
165
|
# oauth2
|
147
166
|
"pyjwt",
|
148
167
|
"requests-oauthlib",
|
149
168
|
# sentry
|
150
169
|
"sentry-sdk",
|
151
|
-
# dev
|
152
|
-
"waitress",
|
153
170
|
# broadcast
|
154
171
|
"redis",
|
155
172
|
# webserver
|
@@ -167,6 +184,9 @@ all = [
|
|
167
184
|
# tests
|
168
185
|
"lxml",
|
169
186
|
"boltons",
|
187
|
+
# waitress
|
188
|
+
"waitress",
|
189
|
+
"Paste",
|
170
190
|
]
|
171
191
|
test_images = ["scikit-image"]
|
172
192
|
|
@@ -175,7 +195,6 @@ test_images = ["scikit-image"]
|
|
175
195
|
prospector = { version = "1.13.3", extras = ["with_bandit", "with_mypy", "with_pyroma", "with_ruff"] }
|
176
196
|
prospector-profile-duplicated = "1.9.0"
|
177
197
|
prospector-profile-utils = "1.14.0"
|
178
|
-
coverage = "7.6.8"
|
179
198
|
junit2html = "31.0.2"
|
180
199
|
pytest = "8.3.4"
|
181
200
|
pytest-cov = "6.0.0"
|
@@ -1,21 +0,0 @@
|
|
1
|
-
import logging
|
2
|
-
from typing import Optional, cast
|
3
|
-
|
4
|
-
from plaster_pastedeploy import Loader as BaseLoader
|
5
|
-
|
6
|
-
from c2cwsgiutils import get_config_defaults
|
7
|
-
|
8
|
-
_LOG = logging.getLogger(__name__)
|
9
|
-
|
10
|
-
|
11
|
-
class Loader(BaseLoader): # type: ignore
|
12
|
-
"""The application loader."""
|
13
|
-
|
14
|
-
def _get_defaults(self, defaults: Optional[dict[str, str]] = None) -> dict[str, str]:
|
15
|
-
d = get_config_defaults()
|
16
|
-
d.update(defaults or {})
|
17
|
-
return cast(dict[str, str], super()._get_defaults(d))
|
18
|
-
|
19
|
-
def __repr__(self) -> str:
|
20
|
-
"""Get the object representation."""
|
21
|
-
return f'c2cwsgiutils.loader.Loader(uri="{self.uri}")'
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/acceptance/package-lock.json
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/request_tracking/__init__.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/sqlalchemylogger/README.md
RENAMED
File without changes
|
{c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/sqlalchemylogger/__init__.py
RENAMED
File without changes
|
{c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/sqlalchemylogger/_filters.py
RENAMED
File without changes
|
{c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/sqlalchemylogger/_models.py
RENAMED
File without changes
|
File without changes
|
{c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/sqlalchemylogger/handlers.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
{c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/stats_pyramid/__init__.py
RENAMED
File without changes
|
File without changes
|
{c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/stats_pyramid/_pyramid_spy.py
RENAMED
File without changes
|
{c2cwsgiutils-6.2.0.dev36 → c2cwsgiutils-6.2.0.dev41}/c2cwsgiutils/templates/index.html.mako
RENAMED
File without changes
|
File without changes
|