django-prometheus 1.2.0.dev101__py3-none-any.whl → 2.6.0.dev22__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.
- django_prometheus/__init__.py +3 -4
- django_prometheus/apps.py +3 -2
- django_prometheus/cache/backends/django_memcached_consul.py +2 -3
- django_prometheus/cache/backends/filebased.py +2 -1
- django_prometheus/cache/backends/locmem.py +2 -1
- django_prometheus/cache/backends/memcached.py +15 -7
- django_prometheus/cache/backends/redis.py +24 -7
- django_prometheus/cache/metrics.py +18 -4
- django_prometheus/conf/__init__.py +27 -0
- django_prometheus/db/__init__.py +2 -0
- django_prometheus/db/backends/mysql/base.py +2 -5
- django_prometheus/db/backends/postgis/__init__.py +0 -0
- django_prometheus/db/backends/postgis/base.py +16 -0
- django_prometheus/db/backends/postgresql/base.py +6 -13
- django_prometheus/db/backends/spatialite/__init__.py +0 -0
- django_prometheus/db/backends/spatialite/base.py +12 -0
- django_prometheus/db/backends/sqlite3/base.py +1 -2
- django_prometheus/db/common.py +15 -15
- django_prometheus/db/metrics.py +18 -6
- django_prometheus/exports.py +8 -14
- django_prometheus/middleware.py +115 -90
- django_prometheus/migrations.py +13 -12
- django_prometheus/models.py +19 -7
- django_prometheus/tests/test_db_wrapper.py +192 -0
- django_prometheus/tests/test_django_prometheus.py +5 -7
- django_prometheus/tests/test_exports.py +1 -1
- django_prometheus/tests/test_testutils.py +99 -100
- django_prometheus/tests/test_utils.py +49 -0
- django_prometheus/testutils.py +169 -139
- django_prometheus/urls.py +3 -4
- django_prometheus/utils.py +3 -3
- django_prometheus-2.6.0.dev22.dist-info/METADATA +339 -0
- django_prometheus-2.6.0.dev22.dist-info/RECORD +43 -0
- {django_prometheus-1.2.0.dev101.dist-info → django_prometheus-2.6.0.dev22.dist-info}/WHEEL +1 -1
- {django_prometheus-1.2.0.dev101.dist-info → django_prometheus-2.6.0.dev22.dist-info/licenses}/LICENSE +0 -1
- django_prometheus-1.2.0.dev101.dist-info/METADATA +0 -37
- django_prometheus-1.2.0.dev101.dist-info/RECORD +0 -36
- {django_prometheus-1.2.0.dev101.dist-info → django_prometheus-2.6.0.dev22.dist-info}/top_level.txt +0 -0
django_prometheus/__init__.py
CHANGED
|
@@ -10,13 +10,12 @@ from django_prometheus import middleware, models
|
|
|
10
10
|
|
|
11
11
|
__all__ = ["middleware", "models", "pip_prometheus"]
|
|
12
12
|
|
|
13
|
-
__version__ =
|
|
13
|
+
__version__ = "2.6.0.dev22"
|
|
14
|
+
|
|
14
15
|
|
|
15
16
|
# Import pip_prometheus to export the pip metrics automatically.
|
|
16
17
|
try:
|
|
17
18
|
import pip_prometheus
|
|
18
19
|
except ImportError:
|
|
19
20
|
# If people don't have pip, don't export anything.
|
|
20
|
-
pass
|
|
21
|
-
|
|
22
|
-
default_app_config = "django_prometheus.apps.DjangoPrometheusConfig"
|
|
21
|
+
pass
|
django_prometheus/apps.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import django_prometheus
|
|
2
1
|
from django.apps import AppConfig
|
|
3
2
|
from django.conf import settings
|
|
3
|
+
|
|
4
|
+
import django_prometheus
|
|
4
5
|
from django_prometheus.exports import SetupPrometheusExportsFromConfig
|
|
5
6
|
from django_prometheus.migrations import ExportMigrations
|
|
6
7
|
|
|
@@ -19,5 +20,5 @@ class DjangoPrometheusConfig(AppConfig):
|
|
|
19
20
|
are usually short-lived), but can be useful for debugging.
|
|
20
21
|
"""
|
|
21
22
|
SetupPrometheusExportsFromConfig()
|
|
22
|
-
if getattr(settings, "PROMETHEUS_EXPORT_MIGRATIONS",
|
|
23
|
+
if getattr(settings, "PROMETHEUS_EXPORT_MIGRATIONS", False):
|
|
23
24
|
ExportMigrations()
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
from __future__ import absolute_import
|
|
2
|
-
|
|
3
1
|
from django_memcached_consul import memcached
|
|
2
|
+
|
|
4
3
|
from django_prometheus.cache.metrics import (
|
|
5
4
|
django_cache_get_total,
|
|
6
5
|
django_cache_hits_total,
|
|
@@ -13,7 +12,7 @@ class MemcachedCache(memcached.MemcachedCache):
|
|
|
13
12
|
|
|
14
13
|
def get(self, key, default=None, version=None):
|
|
15
14
|
django_cache_get_total.labels(backend="django_memcached_consul").inc()
|
|
16
|
-
cached = super(
|
|
15
|
+
cached = super().get(key, default=None, version=version)
|
|
17
16
|
if cached is not None:
|
|
18
17
|
django_cache_hits_total.labels(backend="django_memcached_consul").inc()
|
|
19
18
|
else:
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from django.core.cache.backends import filebased
|
|
2
|
+
|
|
2
3
|
from django_prometheus.cache.metrics import (
|
|
3
4
|
django_cache_get_total,
|
|
4
5
|
django_cache_hits_total,
|
|
@@ -11,7 +12,7 @@ class FileBasedCache(filebased.FileBasedCache):
|
|
|
11
12
|
|
|
12
13
|
def get(self, key, default=None, version=None):
|
|
13
14
|
django_cache_get_total.labels(backend="filebased").inc()
|
|
14
|
-
cached = super(
|
|
15
|
+
cached = super().get(key, default=None, version=version)
|
|
15
16
|
if cached is not None:
|
|
16
17
|
django_cache_hits_total.labels(backend="filebased").inc()
|
|
17
18
|
else:
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from django.core.cache.backends import locmem
|
|
2
|
+
|
|
2
3
|
from django_prometheus.cache.metrics import (
|
|
3
4
|
django_cache_get_total,
|
|
4
5
|
django_cache_hits_total,
|
|
@@ -11,7 +12,7 @@ class LocMemCache(locmem.LocMemCache):
|
|
|
11
12
|
|
|
12
13
|
def get(self, key, default=None, version=None):
|
|
13
14
|
django_cache_get_total.labels(backend="locmem").inc()
|
|
14
|
-
cached = super(
|
|
15
|
+
cached = super().get(key, default=None, version=version)
|
|
15
16
|
if cached is not None:
|
|
16
17
|
django_cache_hits_total.labels(backend="locmem").inc()
|
|
17
18
|
else:
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from django.core.cache.backends import memcached
|
|
2
|
+
|
|
2
3
|
from django_prometheus.cache.metrics import (
|
|
3
4
|
django_cache_get_total,
|
|
4
5
|
django_cache_hits_total,
|
|
@@ -6,14 +7,21 @@ from django_prometheus.cache.metrics import (
|
|
|
6
7
|
)
|
|
7
8
|
|
|
8
9
|
|
|
9
|
-
class
|
|
10
|
-
"""Inherit memcached to add metrics about hit/miss ratio"""
|
|
11
|
-
|
|
10
|
+
class MemcachedPrometheusCacheMixin:
|
|
12
11
|
def get(self, key, default=None, version=None):
|
|
13
12
|
django_cache_get_total.labels(backend="memcached").inc()
|
|
14
|
-
cached = super(
|
|
13
|
+
cached = super().get(key, default=None, version=version)
|
|
15
14
|
if cached is not None:
|
|
16
15
|
django_cache_hits_total.labels(backend="memcached").inc()
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
return cached
|
|
17
|
+
|
|
18
|
+
django_cache_misses_total.labels(backend="memcached").inc()
|
|
19
|
+
return default
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class PyLibMCCache(MemcachedPrometheusCacheMixin, memcached.PyLibMCCache):
|
|
23
|
+
"""Inherit memcached to add metrics about hit/miss ratio"""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class PyMemcacheCache(MemcachedPrometheusCacheMixin, memcached.PyMemcacheCache):
|
|
27
|
+
"""Inherit memcached to add metrics about hit/miss ratio"""
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
from django.core.cache.backends.redis import RedisCache as DjangoRedisCache
|
|
2
|
+
from django_redis import cache, exceptions
|
|
3
|
+
|
|
1
4
|
from django_prometheus.cache.metrics import (
|
|
2
5
|
django_cache_get_fail_total,
|
|
3
6
|
django_cache_get_total,
|
|
4
7
|
django_cache_hits_total,
|
|
5
8
|
django_cache_misses_total,
|
|
6
9
|
)
|
|
7
|
-
from django_redis import cache, exceptions
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
class RedisCache(cache.RedisCache):
|
|
@@ -17,14 +19,29 @@ class RedisCache(cache.RedisCache):
|
|
|
17
19
|
cached = self.client.get(key, default=None, version=version, client=client)
|
|
18
20
|
except exceptions.ConnectionInterrupted as e:
|
|
19
21
|
django_cache_get_fail_total.labels(backend="redis").inc()
|
|
20
|
-
if
|
|
21
|
-
if
|
|
22
|
-
|
|
22
|
+
if self._ignore_exceptions:
|
|
23
|
+
if self._log_ignored_exceptions:
|
|
24
|
+
self.logger.error(str(e))
|
|
23
25
|
return default
|
|
24
26
|
raise
|
|
25
27
|
else:
|
|
26
28
|
if cached is not None:
|
|
27
29
|
django_cache_hits_total.labels(backend="redis").inc()
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return
|
|
30
|
+
return cached
|
|
31
|
+
django_cache_misses_total.labels(backend="redis").inc()
|
|
32
|
+
return default
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class NativeRedisCache(DjangoRedisCache):
|
|
36
|
+
def get(self, key, default=None, version=None):
|
|
37
|
+
django_cache_get_total.labels(backend="native_redis").inc()
|
|
38
|
+
try:
|
|
39
|
+
result = super().get(key, default=None, version=version)
|
|
40
|
+
except Exception:
|
|
41
|
+
django_cache_get_fail_total.labels(backend="native_redis").inc()
|
|
42
|
+
raise
|
|
43
|
+
if result is not None:
|
|
44
|
+
django_cache_hits_total.labels(backend="native_redis").inc()
|
|
45
|
+
return result
|
|
46
|
+
django_cache_misses_total.labels(backend="native_redis").inc()
|
|
47
|
+
return default
|
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
from prometheus_client import Counter
|
|
2
2
|
|
|
3
|
+
from django_prometheus.conf import NAMESPACE
|
|
4
|
+
|
|
3
5
|
django_cache_get_total = Counter(
|
|
4
|
-
"django_cache_get_total",
|
|
6
|
+
"django_cache_get_total",
|
|
7
|
+
"Total get requests on cache",
|
|
8
|
+
["backend"],
|
|
9
|
+
namespace=NAMESPACE,
|
|
5
10
|
)
|
|
6
11
|
django_cache_hits_total = Counter(
|
|
7
|
-
"django_cache_get_hits_total",
|
|
12
|
+
"django_cache_get_hits_total",
|
|
13
|
+
"Total hits on cache",
|
|
14
|
+
["backend"],
|
|
15
|
+
namespace=NAMESPACE,
|
|
8
16
|
)
|
|
9
17
|
django_cache_misses_total = Counter(
|
|
10
|
-
"django_cache_get_misses_total",
|
|
18
|
+
"django_cache_get_misses_total",
|
|
19
|
+
"Total misses on cache",
|
|
20
|
+
["backend"],
|
|
21
|
+
namespace=NAMESPACE,
|
|
11
22
|
)
|
|
12
23
|
django_cache_get_fail_total = Counter(
|
|
13
|
-
"django_cache_get_fail_total",
|
|
24
|
+
"django_cache_get_fail_total",
|
|
25
|
+
"Total get request failures by cache",
|
|
26
|
+
["backend"],
|
|
27
|
+
namespace=NAMESPACE,
|
|
14
28
|
)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from django.conf import settings
|
|
2
|
+
|
|
3
|
+
NAMESPACE = ""
|
|
4
|
+
|
|
5
|
+
PROMETHEUS_LATENCY_BUCKETS = (
|
|
6
|
+
0.01,
|
|
7
|
+
0.025,
|
|
8
|
+
0.05,
|
|
9
|
+
0.075,
|
|
10
|
+
0.1,
|
|
11
|
+
0.25,
|
|
12
|
+
0.5,
|
|
13
|
+
0.75,
|
|
14
|
+
1.0,
|
|
15
|
+
2.5,
|
|
16
|
+
5.0,
|
|
17
|
+
7.5,
|
|
18
|
+
10.0,
|
|
19
|
+
25.0,
|
|
20
|
+
50.0,
|
|
21
|
+
75.0,
|
|
22
|
+
float("inf"),
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
if settings.configured:
|
|
26
|
+
NAMESPACE = getattr(settings, "PROMETHEUS_METRIC_NAMESPACE", NAMESPACE)
|
|
27
|
+
PROMETHEUS_LATENCY_BUCKETS = getattr(settings, "PROMETHEUS_LATENCY_BUCKETS", PROMETHEUS_LATENCY_BUCKETS)
|
django_prometheus/db/__init__.py
CHANGED
|
@@ -6,6 +6,7 @@ from django_prometheus.db.metrics import (
|
|
|
6
6
|
errors_total,
|
|
7
7
|
execute_many_total,
|
|
8
8
|
execute_total,
|
|
9
|
+
query_duration_seconds,
|
|
9
10
|
)
|
|
10
11
|
|
|
11
12
|
__all__ = [
|
|
@@ -15,4 +16,5 @@ __all__ = [
|
|
|
15
16
|
"errors_total",
|
|
16
17
|
"execute_many_total",
|
|
17
18
|
"execute_total",
|
|
19
|
+
"query_duration_seconds",
|
|
18
20
|
]
|
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
from django.db.backends.mysql import base
|
|
2
|
+
|
|
2
3
|
from django_prometheus.db.common import DatabaseWrapperMixin, ExportingCursorWrapper
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
class DatabaseFeatures(base.DatabaseFeatures):
|
|
6
7
|
"""Our database has the exact same features as the base one."""
|
|
7
8
|
|
|
8
|
-
pass
|
|
9
|
-
|
|
10
9
|
|
|
11
10
|
class DatabaseWrapper(DatabaseWrapperMixin, base.DatabaseWrapper):
|
|
12
11
|
CURSOR_CLASS = base.CursorWrapper
|
|
13
12
|
|
|
14
13
|
def create_cursor(self, name=None):
|
|
15
14
|
cursor = self.connection.cursor()
|
|
16
|
-
CursorWrapper = ExportingCursorWrapper(
|
|
17
|
-
self.CURSOR_CLASS, self.alias, self.vendor
|
|
18
|
-
)
|
|
15
|
+
CursorWrapper = ExportingCursorWrapper(self.CURSOR_CLASS, self.alias, self.vendor)
|
|
19
16
|
return CursorWrapper(cursor)
|
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from django.contrib.gis.db.backends.postgis import base
|
|
2
|
+
|
|
3
|
+
from django_prometheus.db.common import DatabaseWrapperMixin, ExportingCursorWrapper
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DatabaseWrapper(DatabaseWrapperMixin, base.DatabaseWrapper):
|
|
7
|
+
def get_connection_params(self):
|
|
8
|
+
conn_params = super().get_connection_params()
|
|
9
|
+
conn_params["cursor_factory"] = ExportingCursorWrapper(conn_params["cursor_factory"], self.alias, self.vendor)
|
|
10
|
+
return conn_params
|
|
11
|
+
|
|
12
|
+
def create_cursor(self, name=None):
|
|
13
|
+
# cursor_factory is set in get_connection_params() so psycopg already
|
|
14
|
+
# creates instrumented cursors. Delegate to Django's implementation to
|
|
15
|
+
# avoid the mixin's create_cursor which is incompatible with psycopg3.
|
|
16
|
+
return base.DatabaseWrapper.create_cursor(self, name=name)
|
|
@@ -1,23 +1,16 @@
|
|
|
1
|
-
import psycopg2.extensions
|
|
2
1
|
from django.db.backends.postgresql import base
|
|
3
|
-
from django_prometheus.db.common import DatabaseWrapperMixin, ExportingCursorWrapper
|
|
4
|
-
|
|
5
2
|
|
|
6
|
-
|
|
7
|
-
"""Our database has the exact same features as the base one."""
|
|
8
|
-
|
|
9
|
-
pass
|
|
3
|
+
from django_prometheus.db.common import DatabaseWrapperMixin, ExportingCursorWrapper
|
|
10
4
|
|
|
11
5
|
|
|
12
6
|
class DatabaseWrapper(DatabaseWrapperMixin, base.DatabaseWrapper):
|
|
13
7
|
def get_connection_params(self):
|
|
14
|
-
conn_params = super(
|
|
15
|
-
conn_params["cursor_factory"] = ExportingCursorWrapper(
|
|
16
|
-
psycopg2.extensions.cursor, self.alias, self.vendor
|
|
17
|
-
)
|
|
8
|
+
conn_params = super().get_connection_params()
|
|
9
|
+
conn_params["cursor_factory"] = ExportingCursorWrapper(conn_params["cursor_factory"], self.alias, self.vendor)
|
|
18
10
|
return conn_params
|
|
19
11
|
|
|
20
12
|
def create_cursor(self, name=None):
|
|
21
|
-
# cursor_factory is
|
|
22
|
-
#
|
|
13
|
+
# cursor_factory is set in get_connection_params() so psycopg already
|
|
14
|
+
# creates instrumented cursors. Delegate to Django's implementation to
|
|
15
|
+
# avoid the mixin's create_cursor which is incompatible with psycopg3.
|
|
23
16
|
return base.DatabaseWrapper.create_cursor(self, name=name)
|
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from django.contrib.gis.db.backends.spatialite import base, features
|
|
2
|
+
from django.db.backends.sqlite3 import base as sqlite_base
|
|
3
|
+
|
|
4
|
+
from django_prometheus.db.common import DatabaseWrapperMixin
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DatabaseFeatures(features.DatabaseFeatures):
|
|
8
|
+
"""Our database has the exact same features as the base one."""
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DatabaseWrapper(DatabaseWrapperMixin, base.DatabaseWrapper):
|
|
12
|
+
CURSOR_CLASS = sqlite_base.SQLiteCursorWrapper
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
from django.db.backends.sqlite3 import base
|
|
2
|
+
|
|
2
3
|
from django_prometheus.db.common import DatabaseWrapperMixin
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
class DatabaseFeatures(base.DatabaseFeatures):
|
|
6
7
|
"""Our database has the exact same features as the base one."""
|
|
7
8
|
|
|
8
|
-
pass
|
|
9
|
-
|
|
10
9
|
|
|
11
10
|
class DatabaseWrapper(DatabaseWrapperMixin, base.DatabaseWrapper):
|
|
12
11
|
CURSOR_CLASS = base.SQLiteCursorWrapper
|
django_prometheus/db/common.py
CHANGED
|
@@ -4,10 +4,11 @@ from django_prometheus.db import (
|
|
|
4
4
|
errors_total,
|
|
5
5
|
execute_many_total,
|
|
6
6
|
execute_total,
|
|
7
|
+
query_duration_seconds,
|
|
7
8
|
)
|
|
8
9
|
|
|
9
10
|
|
|
10
|
-
class ExceptionCounterByType
|
|
11
|
+
class ExceptionCounterByType:
|
|
11
12
|
"""A context manager that counts exceptions by type.
|
|
12
13
|
|
|
13
14
|
Exceptions increment the provided counter, whose last label's name
|
|
@@ -24,7 +25,7 @@ class ExceptionCounterByType(object):
|
|
|
24
25
|
def __init__(self, counter, type_label="type", extra_labels=None):
|
|
25
26
|
self._counter = counter
|
|
26
27
|
self._type_label = type_label
|
|
27
|
-
self._labels = extra_labels
|
|
28
|
+
self._labels = dict(extra_labels) # Copy labels since we modify them.
|
|
28
29
|
|
|
29
30
|
def __enter__(self):
|
|
30
31
|
pass
|
|
@@ -35,46 +36,45 @@ class ExceptionCounterByType(object):
|
|
|
35
36
|
self._counter.labels(**self._labels).inc()
|
|
36
37
|
|
|
37
38
|
|
|
38
|
-
class DatabaseWrapperMixin
|
|
39
|
+
class DatabaseWrapperMixin:
|
|
39
40
|
"""Extends the DatabaseWrapper to count connections and cursors."""
|
|
40
41
|
|
|
41
42
|
def get_new_connection(self, *args, **kwargs):
|
|
42
43
|
connections_total.labels(self.alias, self.vendor).inc()
|
|
43
44
|
try:
|
|
44
|
-
return super(
|
|
45
|
+
return super().get_new_connection(*args, **kwargs)
|
|
45
46
|
except Exception:
|
|
46
47
|
connection_errors_total.labels(self.alias, self.vendor).inc()
|
|
47
48
|
raise
|
|
48
49
|
|
|
49
50
|
def create_cursor(self, name=None):
|
|
50
|
-
return self.connection.cursor(
|
|
51
|
-
factory=ExportingCursorWrapper(self.CURSOR_CLASS, self.alias, self.vendor)
|
|
52
|
-
)
|
|
51
|
+
return self.connection.cursor(factory=ExportingCursorWrapper(self.CURSOR_CLASS, self.alias, self.vendor))
|
|
53
52
|
|
|
54
53
|
|
|
55
54
|
def ExportingCursorWrapper(cursor_class, alias, vendor):
|
|
56
55
|
"""Returns a CursorWrapper class that knows its database's alias and
|
|
57
56
|
vendor name.
|
|
58
57
|
"""
|
|
58
|
+
labels = {"alias": alias, "vendor": vendor}
|
|
59
59
|
|
|
60
60
|
class CursorWrapper(cursor_class):
|
|
61
61
|
"""Extends the base CursorWrapper to count events."""
|
|
62
62
|
|
|
63
63
|
def execute(self, *args, **kwargs):
|
|
64
64
|
execute_total.labels(alias, vendor).inc()
|
|
65
|
-
with
|
|
66
|
-
|
|
65
|
+
with (
|
|
66
|
+
query_duration_seconds.labels(**labels).time(),
|
|
67
|
+
ExceptionCounterByType(errors_total, extra_labels=labels),
|
|
67
68
|
):
|
|
68
|
-
return super(
|
|
69
|
+
return super().execute(*args, **kwargs)
|
|
69
70
|
|
|
70
71
|
def executemany(self, query, param_list, *args, **kwargs):
|
|
71
72
|
execute_total.labels(alias, vendor).inc(len(param_list))
|
|
72
73
|
execute_many_total.labels(alias, vendor).inc(len(param_list))
|
|
73
|
-
with
|
|
74
|
-
|
|
74
|
+
with (
|
|
75
|
+
query_duration_seconds.labels(**labels).time(),
|
|
76
|
+
ExceptionCounterByType(errors_total, extra_labels=labels),
|
|
75
77
|
):
|
|
76
|
-
return super(
|
|
77
|
-
query, param_list, *args, **kwargs
|
|
78
|
-
)
|
|
78
|
+
return super().executemany(query, param_list, *args, **kwargs)
|
|
79
79
|
|
|
80
80
|
return CursorWrapper
|
django_prometheus/db/metrics.py
CHANGED
|
@@ -1,31 +1,34 @@
|
|
|
1
|
-
from prometheus_client import Counter
|
|
1
|
+
from prometheus_client import Counter, Histogram
|
|
2
|
+
|
|
3
|
+
from django_prometheus.conf import NAMESPACE, PROMETHEUS_LATENCY_BUCKETS
|
|
2
4
|
|
|
3
5
|
connections_total = Counter(
|
|
4
6
|
"django_db_new_connections_total",
|
|
5
7
|
"Counter of created connections by database and by vendor.",
|
|
6
8
|
["alias", "vendor"],
|
|
9
|
+
namespace=NAMESPACE,
|
|
7
10
|
)
|
|
8
11
|
|
|
9
12
|
connection_errors_total = Counter(
|
|
10
13
|
"django_db_new_connection_errors_total",
|
|
11
14
|
"Counter of connection failures by database and by vendor.",
|
|
12
15
|
["alias", "vendor"],
|
|
16
|
+
namespace=NAMESPACE,
|
|
13
17
|
)
|
|
14
18
|
|
|
15
19
|
execute_total = Counter(
|
|
16
20
|
"django_db_execute_total",
|
|
17
|
-
(
|
|
18
|
-
"Counter of executed statements by database and by vendor, including"
|
|
19
|
-
" bulk executions."
|
|
20
|
-
),
|
|
21
|
+
("Counter of executed statements by database and by vendor, including bulk executions."),
|
|
21
22
|
["alias", "vendor"],
|
|
23
|
+
namespace=NAMESPACE,
|
|
22
24
|
)
|
|
23
25
|
|
|
24
26
|
|
|
25
27
|
execute_many_total = Counter(
|
|
26
28
|
"django_db_execute_many_total",
|
|
27
|
-
("Counter of executed statements in bulk operations by database and
|
|
29
|
+
("Counter of executed statements in bulk operations by database and by vendor."),
|
|
28
30
|
["alias", "vendor"],
|
|
31
|
+
namespace=NAMESPACE,
|
|
29
32
|
)
|
|
30
33
|
|
|
31
34
|
|
|
@@ -33,4 +36,13 @@ errors_total = Counter(
|
|
|
33
36
|
"django_db_errors_total",
|
|
34
37
|
("Counter of execution errors by database, vendor and exception type."),
|
|
35
38
|
["alias", "vendor", "type"],
|
|
39
|
+
namespace=NAMESPACE,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
query_duration_seconds = Histogram(
|
|
43
|
+
"django_db_query_duration_seconds",
|
|
44
|
+
("Histogram of query duration by database and vendor."),
|
|
45
|
+
["alias", "vendor"],
|
|
46
|
+
buckets=PROMETHEUS_LATENCY_BUCKETS,
|
|
47
|
+
namespace=NAMESPACE,
|
|
36
48
|
)
|
django_prometheus/exports.py
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import os
|
|
3
|
-
import socket
|
|
4
3
|
import threading
|
|
5
4
|
|
|
6
5
|
import prometheus_client
|
|
7
|
-
from prometheus_client import multiprocess
|
|
8
|
-
|
|
9
6
|
from django.conf import settings
|
|
10
7
|
from django.http import HttpResponse
|
|
8
|
+
from prometheus_client import multiprocess
|
|
11
9
|
|
|
12
10
|
try:
|
|
13
11
|
# Python 2
|
|
@@ -23,7 +21,7 @@ logger = logging.getLogger(__name__)
|
|
|
23
21
|
def SetupPrometheusEndpointOnPort(port, addr=""):
|
|
24
22
|
"""Exports Prometheus metrics on an HTTPServer running in its own thread.
|
|
25
23
|
|
|
26
|
-
The server runs on the given port and is by default
|
|
24
|
+
The server runs on the given port and is by default listening on
|
|
27
25
|
all interfaces. This HTTPServer is fully independent of Django and
|
|
28
26
|
its stack. This offers the advantage that even if Django becomes
|
|
29
27
|
unable to respond, the HTTPServer will continue to function and
|
|
@@ -52,7 +50,7 @@ class PrometheusEndpointServer(threading.Thread):
|
|
|
52
50
|
|
|
53
51
|
def __init__(self, httpd, *args, **kwargs):
|
|
54
52
|
self.httpd = httpd
|
|
55
|
-
super(
|
|
53
|
+
super().__init__(*args, **kwargs)
|
|
56
54
|
|
|
57
55
|
def run(self):
|
|
58
56
|
self.httpd.serve_forever()
|
|
@@ -86,18 +84,16 @@ def SetupPrometheusEndpointOnPortRange(port_range, addr=""):
|
|
|
86
84
|
for port in port_range:
|
|
87
85
|
try:
|
|
88
86
|
httpd = HTTPServer((addr, port), prometheus_client.MetricsHandler)
|
|
89
|
-
except
|
|
87
|
+
except OSError:
|
|
90
88
|
# Python 2 raises socket.error, in Python 3 socket.error is an
|
|
91
89
|
# alias for OSError
|
|
92
90
|
continue # Try next port
|
|
93
91
|
thread = PrometheusEndpointServer(httpd)
|
|
94
92
|
thread.daemon = True
|
|
95
93
|
thread.start()
|
|
96
|
-
logger.info("Exporting Prometheus /metrics/ on port
|
|
94
|
+
logger.info(f"Exporting Prometheus /metrics/ on port {port}")
|
|
97
95
|
return port # Stop trying ports at this point
|
|
98
|
-
logger.warning(
|
|
99
|
-
"Cannot export Prometheus /metrics/ - " "no available ports in supplied range"
|
|
100
|
-
)
|
|
96
|
+
logger.warning("Cannot export Prometheus /metrics/ - no available ports in supplied range")
|
|
101
97
|
return None
|
|
102
98
|
|
|
103
99
|
|
|
@@ -117,12 +113,10 @@ def ExportToDjangoView(request):
|
|
|
117
113
|
|
|
118
114
|
You can use django_prometheus.urls to map /metrics to this view.
|
|
119
115
|
"""
|
|
120
|
-
if "prometheus_multiproc_dir" in os.environ:
|
|
116
|
+
if "PROMETHEUS_MULTIPROC_DIR" in os.environ or "prometheus_multiproc_dir" in os.environ:
|
|
121
117
|
registry = prometheus_client.CollectorRegistry()
|
|
122
118
|
multiprocess.MultiProcessCollector(registry)
|
|
123
119
|
else:
|
|
124
120
|
registry = prometheus_client.REGISTRY
|
|
125
121
|
metrics_page = prometheus_client.generate_latest(registry)
|
|
126
|
-
return HttpResponse(
|
|
127
|
-
metrics_page, content_type=prometheus_client.CONTENT_TYPE_LATEST
|
|
128
|
-
)
|
|
122
|
+
return HttpResponse(metrics_page, content_type=prometheus_client.CONTENT_TYPE_LATEST)
|