Gateway Metrics

Prometheus-format counters, gauges, and latency histograms from the PraisonAI gateway

Scrape GET /metrics on the PraisonAI gateway to feed Prometheus — counters, gauges, and latency histograms for every hop of the message flow, zero new dependencies.

python
from praisonaiagents import Agent
from praisonai.bots import GatewayMetrics

agent = Agent(name="assistant", instructions="Be helpful")

metrics = GatewayMetrics()
with metrics.timer("gateway_turn_latency_seconds", labels={"platform": "telegram"}):
    agent.start("Handle user messages while metrics export on GET /metrics")
bash
curl -sH "Authorization: Bearer $PRAISONAI_GATEWAY_AUTH_TOKEN" http://localhost:8765/metrics

The user scrapes GET /metrics while messaging the gateway; counters, gauges, and latency histograms record inbound, agent, and outbound hops.

Diagram

counters

gauges

📨 Inbound

🛰️ Gateway

🤖 Agent

📤 Outbound

📊 /metrics

Prometheus

Quick Start

  1. Start the gateway with auth_token set
    yaml
    gateway:
      host: "0.0.0.0"
      port: 8765
      auth_token: ${PRAISONAI_GATEWAY_AUTH_TOKEN}
    bash
    export PRAISONAI_GATEWAY_AUTH_TOKEN=secret
    praisonai gateway start --config gateway.yaml
  2. Scrape the metrics endpoint
    bash
    curl -sH "Authorization: Bearer $PRAISONAI_GATEWAY_AUTH_TOKEN" \
      http://localhost:8765/metrics
  3. Read the Prometheus exposition output
    # HELP channel_restarts_total Total channel restarts performed by supervision.
    # TYPE channel_restarts_total counter
    channel_restarts_total{channel="telegram"} 2.0
    # HELP messages_dispatched_total Total inbound messages dispatched to an agent run.
    # TYPE messages_dispatched_total counter
    messages_dispatched_total 184.0
    # HELP messages_inbound_total Total inbound messages received by the gateway.
    # TYPE messages_inbound_total counter
    messages_inbound_total 187.0
    # HELP outbound_failed_total Total outbound messages that failed delivery.
    # TYPE outbound_failed_total counter
    outbound_failed_total{channel="discord"} 1.0
    # HELP outbound_sent_total Total outbound messages successfully delivered.
    # TYPE outbound_sent_total counter
    outbound_sent_total{channel="telegram"} 184.0
    # HELP active_sessions Current number of active gateway sessions.
    # TYPE active_sessions gauge
    active_sessions 5.0
    # HELP channel_recoveries Total supervision recoveries per channel.
    # TYPE channel_recoveries gauge
    channel_recoveries{channel="telegram"} 2.0
    # HELP gateway_turn_latency_seconds Turn latency (inbound received -> reply delivered).
    # TYPE gateway_turn_latency_seconds histogram
    gateway_turn_latency_seconds_bucket{platform="telegram",le="0.1"} 0
    gateway_turn_latency_seconds_bucket{platform="telegram",le="0.25"} 1
    gateway_turn_latency_seconds_bucket{platform="telegram",le="0.5"} 3
    gateway_turn_latency_seconds_bucket{platform="telegram",le="1.0"} 5
    gateway_turn_latency_seconds_bucket{platform="telegram",le="+Inf"} 5
    gateway_turn_latency_seconds_sum{platform="telegram"} 1.87
    gateway_turn_latency_seconds_count{platform="telegram"} 5

How It Works

Diagram
sequenceDiagram
    participant Scraper
    participant Gateway as Gateway /metrics
    participant Auth as _check_auth
    participant Refresh as _refresh_metric_gauges
    participant Registry as GatewayMetrics

    Scraper->>Gateway: GET /metrics
    Gateway->>Auth: token check
    Auth-->>Gateway: 200 OK
    Gateway->>Refresh: sample live gauges
    Refresh->>Registry: set_gauge("active_sessions", ...)
    Refresh->>Registry: set_gauge("channel_recoveries", ...)
    Gateway->>Registry: render_prometheus()
    Registry-->>Gateway: text/plain; version=0.0.4
    Gateway-->>Scraper: Prometheus exposition

Gauges are sampled on every scrape — not pushed on a timer. The /metrics endpoint calls _refresh_metric_gauges() before rendering, so active_sessions and channel_recoveries always reflect the live gateway state.


Configuration Options

Pick the metric type that matches what you are measuring.

Diagram

A monotonic count of events

A point-in-time level

A duration or size distribution

What are you
measuring?

Counter · inc

Gauge · set_gauge

Histogram · observe / timer

Authentication

The /metrics endpoint uses the same _check_auth gate as other operational endpoints (e.g. /info).

MethodHow
Cookie sessionActive browser session
Loopback bypassRequests from 127.0.0.1 / ::1 bypass token check
Bearer tokenAuthorization: Bearer <token> header
Query param?token=<token> (deprecated)

Returns 401 if token is required but missing; 403 on token mismatch.

Counters

NameHelp
messages_inbound_totalTotal inbound messages received by the gateway.
messages_dispatched_totalTotal inbound messages dispatched to an agent run.
messages_duplicate_totalTotal inbound messages dropped as duplicates.
outbound_sent_totalTotal outbound messages successfully delivered.
outbound_failed_totalTotal outbound messages that failed delivery.
approval_pending_totalTotal approval requests created.
approval_decided_totalTotal approval requests decided (allowed or denied).
channel_errors_totalTotal channel errors observed by supervision.
channel_restarts_totalTotal channel restarts performed by supervision.

Gauges

NameHelp
outbox_depthCurrent number of messages pending outbound delivery.
approval_pendingCurrent number of approvals awaiting a decision.
active_sessionsCurrent number of active gateway sessions.
channel_recoveriesTotal supervision recoveries per channel.

Labels: Counters and gauges accept an arbitrary labels dict (string→string). Channel-scoped metrics use labels={"channel": <name>}. Labels are rendered as sorted, deterministic name{a="b",c="d"} value.

Histograms

Histograms record latency distributions per hop, so you can compute p50/p95/p99 straight from /metrics.

Diagram

observe seconds

_bucket _sum _count

⏱️ Stage

📊 Histogram

📈 /metrics

p50 / p95 / p99

Five histograms are pre-registered with help text:

NameHelp
gateway_turn_latency_secondsTurn latency (inbound received -> reply delivered).
gateway_llm_latency_secondsLLM-call latency in seconds.
gateway_tool_latency_secondsTool-call latency in seconds.
gateway_queue_wait_secondsAdmission/queue wait time before dispatch.
gateway_outbound_latency_secondsOutbound send latency in seconds.

observe(name, seconds, buckets=DEFAULT_BUCKETS, labels=None) records a duration into cumulative buckets plus running _sum / _count. timer(name, ...) is a context manager that times its body with time.perf_counter() and calls observe. Both default to DEFAULT_BUCKETS:

python
DEFAULT_BUCKETS = (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0)

Common Patterns

Recording a custom counter

python
from praisonai.gateway.server import WebSocketGateway

gateway = WebSocketGateway.from_config_file("gateway.yaml")

gateway.record_metric("messages_inbound_total", labels={"channel": "telegram"})

Fetching a JSON snapshot in tests

python
snap = gateway.metrics_snapshot()
# {
#   "counters":  {"messages_inbound_total{channel=\"telegram\"}": 1.0, ...},
#   "gauges":    {"active_sessions": 5.0, ...},
#   "histograms":{
#       "gateway_turn_latency_seconds_count":               2.0,
#       "gateway_turn_latency_seconds_sum":                 1.8,
#       "gateway_turn_latency_seconds_bucket{le=\"0.5\"}":  1.0,
#       "gateway_turn_latency_seconds_bucket{le=\"2.5\"}":  2.0,
#   },
# }

Prometheus scrape config

yaml
scrape_configs:
  - job_name: praisonai-gateway
    metrics_path: /metrics
    bearer_token: ${PRAISONAI_GATEWAY_AUTH_TOKEN}
    static_configs:
      - targets: ["gateway.internal:8765"]

Using GatewayMetrics directly

python
from praisonai.bots import GatewayMetrics

metrics = GatewayMetrics()

metrics.inc("messages_inbound_total", labels={"channel": "telegram"})
metrics.set_gauge("active_sessions", 5.0)

print(metrics.counter_value("messages_inbound_total", labels={"channel": "telegram"}))
print(metrics.render_prometheus())

Timing a stage with timer()

python
from praisonai.bots import GatewayMetrics

metrics = GatewayMetrics()

with metrics.timer("gateway_turn_latency_seconds", labels={"platform": "telegram"}):
    reply = run_turn(event)

Recording a measured duration with observe()

python
from praisonai.bots import GatewayMetrics

metrics = GatewayMetrics()

metrics.observe("gateway_llm_latency_seconds", llm_elapsed_seconds)
metrics.observe(
    "gateway_tool_latency_seconds",
    tool_elapsed_seconds,
    labels={"tool": "search"},
)

Custom buckets for a different latency profile

python
from praisonai.bots import GatewayMetrics

metrics = GatewayMetrics()

metrics.observe(
    "gateway_queue_wait_seconds",
    wait_seconds,
    buckets=(0.001, 0.005, 0.01, 0.05, 0.1),
)

The bucket set is pinned to the histogram name on first observation — re-observing with different bounds raises ValueError.


Best Practices

Set auth_token before binding to non-loopback

Always configure auth_token in gateway.yaml when your gateway binds to 0.0.0.0 or a public address. Without it, /metrics is open to the network.

Use a scrape interval of 5s or longer

Gauges are sampled on each scrape. Scraping more frequently than every 5 seconds wastes resources without adding useful resolution — gateway state changes at human timescales.

Alert on outbound_failed_total and channel_restarts_total

These two counters surface delivery problems immediately. Configure Prometheus alerting rules on their rates:

yaml
- alert: GatewayOutboundFailures
  expr: rate(outbound_failed_total[5m]) > 0
  for: 1m
- alert: GatewayChannelRestarts
  expr: rate(channel_restarts_total[5m]) > 0.1
  for: 2m
Don't depend on push frequency for gauges

active_sessions, outbox_depth, and approval_pending are point-in-time samples taken at scrape time — they are not time-averaged. Use them as snapshots, not as rate inputs.

Alert on p95/p99 turn latency

Turn-latency histograms feed SLOs directly. Use histogram_quantile over the _bucket rate to alert on p95:

yaml
- alert: GatewayTurnLatencyHigh
  expr: |
    histogram_quantile(
      0.95,
      sum by (le, platform) (rate(gateway_turn_latency_seconds_bucket[5m]))
    ) > 3
  for: 5m

Related