orchestrata_ultimate.gateway.metrics

Reference › orchestrata_ultimate › gateway › metrics

metrics defines the Prometheus instruments of the gateway (SPEC-014 US-009) behind a set of small recording functions. session calls connection_opened and connection_closed, protocol calls message_received and message_sent, rpc calls rpc_latency with the method name and elapsed seconds, pubsub calls subscription_added and subscription_removed with the topic, and auth_handler calls auth_success and auth_failure. The /metrics route in http_surface exposes the result.

_get_or_create registers each instrument once and reuses it afterwards, so importing the module repeatedly does not duplicate metrics. Every helper runs through _safe, which logs a warning and never raises, so a fault in instrumentation cannot break the gateway operation it measures. See gateway.

At a glance

Source src/orchestrata_ultimate/gateway/metrics.py
Lines of code 214
Classes 0
Functions 11
Imports (internal) orchestrata_ultimate.core.base.traces
Imported by orchestrata_ultimate.gateway.auth_handler, orchestrata_ultimate.gateway.protocol, orchestrata_ultimate.gateway.pubsub, orchestrata_ultimate.gateway.rpc, orchestrata_ultimate.gateway.session
Tests that import it tests/gateway/test_gateway_metrics.py, tests/generated_gateway/test_metrics_generated.py

Class diagram

No classes; see Functions.

Dependency diagram

Dependency diagram orchestrata_ultimate.gateway.metrics

API

Functions

Function Signature Summary
_safe _safe(op: Callable[[], None], warning: str) -> None Run op, logging (never raising) on failure.
_get_or_create _get_or_create(metric_cls: type[Any], name: str, documentation: str, labelnames: tuple[str, ...] = ()) -> Any Return the named collector, creating it once on the default registry.
connection_opened traces connection_opened() -> None Record a newly registered WebSocket connection.
connection_closed traces connection_closed() -> None Record a WebSocket connection going away.
message_received traces message_received() -> None Record one inbound WebSocket frame.
message_sent traces message_sent() -> None Record one outbound WebSocket frame.
rpc_latency traces rpc_latency(method: str, seconds: float) -> None Record one RPC dispatch's latency, labeled by method.
subscription_added traces subscription_added(topic: str) -> None Record one new subscription on topic.
subscription_removed traces subscription_removed(topic: str) -> None Record one subscription removed from topic.
auth_success traces auth_success() -> None Record a successful authentication verdict.
auth_failure traces auth_failure() -> None Record a failed authentication verdict.

Source

Permalink: https://gitlab.com/MSamy/orchestrata-ultimate-architecture/-/blob/a4cbc2bdf/src/orchestrata_ultimate/gateway/metrics.py

"""Prometheus instruments for the WebSocket gateway (SPEC-014 US-009).

Registers the eight ``orchestrata_gateway_*`` metrics named in
REQ-014-142..149 on the ``prometheus_client`` DEFAULT registry -- the same
registry ``http_surface.py``'s ``/metrics`` endpoint serves via
``generate_latest()`` (REQ-014-141), so anything registered here is
scrape-visible with no extra wiring.

Registration is idempotent (get-or-create by name): re-importing this
module, or constructing the gateway more than once in the same process
(parallel test workers, repeated fixtures), must never raise
prometheus_client's "Duplicated timeseries in CollectorRegistry".

Every ``record_*``/``observe_*`` helper below swallows its own failures --
a metrics-recording bug must never break the gateway operation it is
instrumenting (session design ruling, US-009).

Naming note (REQ-014-143): ``orchestrata_gateway_total_connections`` has
"total" in the middle of the name rather than as an OpenMetrics-style
trailing ``_total`` suffix. ``prometheus_client``'s ``Counter`` type
*unconditionally* appends ``_total`` to both the rendered sample name and
the ``# TYPE``/``# HELP`` comment lines (see
``prometheus_client.exposition.generate_latest``), which would render this
metric as ``orchestrata_gateway_total_connections_total`` -- violating the
exact-name contract. A ``Gauge`` renders the given name verbatim, so this
one instrument is implemented as a ``Gauge`` that is only ever
incremented (never decremented), i.e. a monotonic running total. Every
other instrument here uses the prometheus_client type its REQ names.
"""

from __future__ import annotations

import logging
from collections.abc import Callable
from typing import Any

from prometheus_client import REGISTRY, Counter, Gauge, Histogram

from orchestrata_ultimate.core.base.traces import traces

logger = logging.getLogger(__name__)


def _safe(op: Callable[[], None], warning: str) -> None:
    """Run *op*, logging (never raising) on failure.

    Every ``record_*``/``observe_*`` helper below goes through here -- a
    bug in the metrics layer must never break the gateway operation it is
    instrumenting (US-009 design ruling).
    """
    try:
        op()
    except Exception:
        logger.warning(warning, exc_info=True)


def _get_or_create(
    metric_cls: type[Any],
    name: str,
    documentation: str,
    labelnames: tuple[str, ...] = (),
) -> Any:
    """Return the named collector, creating it once on the default registry.

    ``prometheus_client`` raises ``ValueError`` on a second registration of
    the same metric name. This module is imported by both ``http_surface``
    and the modules that record against these instruments, and tests may
    reimport/reload it -- so look the collector up by name first, and fall
    back to catching the race if two callers create it concurrently.
    """
    existing = REGISTRY._names_to_collectors.get(name)
    if existing is not None:
        return existing
    try:
        return metric_cls(name, documentation, labelnames)
    except ValueError:
        return REGISTRY._names_to_collectors[name]


CONNECTED_CLIENTS = _get_or_create(
    Gauge,
    "orchestrata_gateway_connected_clients",
    "Current number of active WebSocket connections to the gateway.",
)

# See module docstring: implemented as a Gauge (inc-only) so the exact
# name REQ-014-143 pins is rendered verbatim -- a real Counter would
# render this as "..._total_total"-adjacent (the auto-appended "_total"
# stacking on the word "total" already in the name).
TOTAL_CONNECTIONS = _get_or_create(
    Gauge,
    "orchestrata_gateway_total_connections",
    "Total WebSocket connections accepted by the gateway since start "
    "(monotonic; never decremented).",
)

MESSAGES_RECEIVED = _get_or_create(
    Counter,
    "orchestrata_gateway_messages_received_total",
    "Total inbound WebSocket messages processed by the gateway.",
)

MESSAGES_SENT = _get_or_create(
    Counter,
    "orchestrata_gateway_messages_sent_total",
    "Total outbound WebSocket messages sent by the gateway.",
)

RPC_LATENCY_SECONDS = _get_or_create(
    Histogram,
    "orchestrata_gateway_rpc_latency_seconds",
    "RPC dispatch latency in seconds, labeled by method.",
    ("method",),
)

SUBSCRIPTION_COUNT = _get_or_create(
    Gauge,
    "orchestrata_gateway_subscription_count",
    "Current number of active subscriptions, labeled by topic.",
    ("topic",),
)

AUTH_SUCCESS = _get_or_create(
    Counter,
    "orchestrata_gateway_auth_success_total",
    "Total successful gateway authentication attempts.",
)

AUTH_FAILURE = _get_or_create(
    Counter,
    "orchestrata_gateway_auth_failure_total",
    "Total failed gateway authentication attempts.",
)


@traces(spec="014", story="US-009", reqs=["REQ-014-142", "REQ-014-143"])
def connection_opened() -> None:
    """Record a newly registered WebSocket connection."""

    def _do() -> None:
        CONNECTED_CLIENTS.inc()
        TOTAL_CONNECTIONS.inc()

    _safe(_do, "Failed to record connection-opened metric")


@traces(spec="014", story="US-009", reqs=["REQ-014-142"])
def connection_closed() -> None:
    """Record a WebSocket connection going away."""
    _safe(CONNECTED_CLIENTS.dec, "Failed to record connection-closed metric")


@traces(spec="014", story="US-009", reqs=["REQ-014-144"])
def message_received() -> None:
    """Record one inbound WebSocket frame."""
    _safe(MESSAGES_RECEIVED.inc, "Failed to record message-received metric")


@traces(spec="014", story="US-009", reqs=["REQ-014-145"])
def message_sent() -> None:
    """Record one outbound WebSocket frame."""
    _safe(MESSAGES_SENT.inc, "Failed to record message-sent metric")


@traces(spec="014", story="US-009", reqs=["REQ-014-146"])
def rpc_latency(method: str, seconds: float) -> None:
    """Record one RPC dispatch's latency, labeled by method.

    Args:
        method: RPC method name (e.g. ``"gateway.ping"``).
        seconds: Wall-clock dispatch duration in seconds.
    """
    _safe(
        lambda: RPC_LATENCY_SECONDS.labels(method=method).observe(seconds),
        "Failed to record rpc-latency metric",
    )


@traces(spec="014", story="US-009", reqs=["REQ-014-147"])
def subscription_added(topic: str) -> None:
    """Record one new subscription on *topic*.

    Args:
        topic: The topic pattern the subscription was created for.
    """
    _safe(
        lambda: SUBSCRIPTION_COUNT.labels(topic=topic).inc(),
        "Failed to record subscription-added metric",
    )


@traces(spec="014", story="US-009", reqs=["REQ-014-147"])
def subscription_removed(topic: str) -> None:
    """Record one subscription removed from *topic*.

    Args:
        topic: The topic pattern the subscription was removed from.
    """
    _safe(
        lambda: SUBSCRIPTION_COUNT.labels(topic=topic).dec(),
        "Failed to record subscription-removed metric",
    )


@traces(spec="014", story="US-009", reqs=["REQ-014-148"])
def auth_success() -> None:
    """Record a successful authentication verdict."""
    _safe(AUTH_SUCCESS.inc, "Failed to record auth-success metric")


@traces(spec="014", story="US-009", reqs=["REQ-014-149"])
def auth_failure() -> None:
    """Record a failed authentication verdict."""
    _safe(AUTH_FAILURE.inc, "Failed to record auth-failure metric")