Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix/feat: register metrics only once and add hit-miss #206

Merged
merged 1 commit into from
Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 25 additions & 16 deletions cashews/contrib/prometheus.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,38 @@
from typing import Optional

from prometheus_client import Histogram
from prometheus_client import Counter, Histogram

from cashews import cache
from cashews._typing import Middleware
from cashews.backends.interface import Backend
from cashews.commands import Command

_DEFAULT_METRIC = Histogram(
"cashews_operations_latency_seconds",
"Latency of different operations with a cache",
labelnames=["operation", "backend_class", "tag"],
)
_HIT_MISS = Counter(
"cashews_get_operation",
"Count of hits or missed GET operations",
labelnames=["result", "backend_class", "tag"],
)

def create_metrics_middleware(latency_metric: Optional[Histogram] = None, with_tag: bool = False) -> Middleware:
_DEFAULT_METRIC = Histogram(
"cashews_operations_latency_seconds",
"Latency of different operations with a cache",
labelnames=["operation", "backend_class"] if not with_tag else ["operation", "backend_class", "tag"],
)
_latency_metric = latency_metric or _DEFAULT_METRIC

def create_metrics_middleware(with_tag: bool = False) -> Middleware:
async def metrics_middleware(call, cmd: Command, backend: Backend, *args, **kwargs):
with _latency_metric.time() as metric:
metric.labels(operation=cmd.value, backend_class=backend.__class__.__name__)
with _DEFAULT_METRIC.time() as metric:
tag = ""

if with_tag and "key" in kwargs:
tags = cache.get_key_tags(kwargs["key"])
tag = next((t for t in tags), None)
if tag:
metric.labels(tag=tag)
return await call(*args, **kwargs)
tag = next((t for t in tags), "")
metric.labels(operation=cmd.value, backend_class=backend.__class__.__name__, tag=tag)
result = await call(*args, **kwargs)
if cmd is Command.GET:
if result is not kwargs["default"]:
op_result = "hit"
else:
op_result = "miss"
_HIT_MISS.labels(result=op_result, backend_class=backend.__class__.__name__, tag=tag).inc()
return result

return metrics_middleware
10 changes: 8 additions & 2 deletions cashews/wrapper/wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,13 @@ def _with_middlewares_for_backend(self, cmd: Command, backend, middlewares):
call = partial(middleware, call, cmd, backend)
return call

def setup(self, settings_url: str, middlewares: tuple = (), prefix: str = default_prefix, **kwargs) -> Backend:
def setup(
self,
settings_url: str,
middlewares: tuple = (),
prefix: str = default_prefix,
**kwargs,
) -> Backend:
backend_class, params = settings_url_parse(settings_url)
params.update(kwargs)

Expand All @@ -76,7 +82,7 @@ def _check_setup(self) -> None:
def _add_backend(self, backend: Backend, middlewares=(), prefix: str = default_prefix) -> None:
self._backends[prefix] = (
backend,
tuple(self._default_middlewares) + middlewares,
middlewares + tuple(self._default_middlewares),
)

async def init(self, *args, **kwargs) -> None:
Expand Down
2 changes: 1 addition & 1 deletion tests/test_intergations/test_prom.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
def _middleware():
from cashews.contrib.prometheus import create_metrics_middleware

return create_metrics_middleware()
return create_metrics_middleware(with_tag=True)


async def test_smoke(middleware):
Expand Down
Loading