Skip to content

Monitoring and Metrics

LoxiLB exposes Prometheus metrics for load balancing, system pressure, AI admission, token quotas, engine-specific routing, and fullproxy QoS. Use the metrics to distinguish Gateway pressure from host pressure and policy denials from backend failures.

Enable and scrape metrics

Metrics collection is disabled by default. Enable it at process startup with -p or --prometheus, or dynamically through the authenticated control plane. While disabled, /metrics returns 503.

export CONTROL_API="https://gateway.example.com/netlox/v1"
install -m 600 /dev/null ./control-plane.headers
printf 'Authorization: Bearer %s\n' "$CONTROL_PLANE_TOKEN" > ./control-plane.headers

curl --fail-with-body --silent --show-error \
  --request POST \
  --header @control-plane.headers \
  "$CONTROL_API/config/metrics"

curl --fail-with-body --silent --show-error \
  --header @control-plane.headers \
  "$CONTROL_API/config/metrics" | jq .

The Prometheus scrape route is GET /netlox/v1/metrics. Its current API contract explicitly exempts it from bearer authentication so Prometheus can scrape without a short-lived user token.

Unauthenticated does not mean public

Metrics expose service, model, tenant, endpoint, and policy activity. Restrict the listener with network policy, firewall rules, or a trusted monitoring proxy. Use TLS across untrusted networks and keep tenant/model labels free of secrets or personal information.

scrape_configs:
  - job_name: loxilb-inference-gateway
    metrics_path: /netlox/v1/metrics
    scrape_interval: 10s
    scheme: https
    static_configs:
      - targets: ["gateway.example.com"]

The bundled monitoring stack and dashboards are available under the public repository's deploy/monitoring/ directory. Review image versions, network bindings, credentials, and TLS settings before using it outside a lab.

CPU scope: Gateway container versus host

flowchart LR
    P["Gateway metrics sampler"] --> C{"Container detected and<br/>cgroup CPU readable?"}
    C -->|yes| S["system CPU gauge<br/>whole cgroup / CPU allowance"]
    C -->|no| F["system CPU gauge<br/>host /proc/stat fallback"]
    P --> H["host CPU gauge<br/>always host /proc/stat"]

    style S fill:#e8f5e9,stroke:#43a047
    style F fill:#fff9c4,stroke:#f9a825
    style H fill:#e1f5fe,stroke:#0288d1
Metric Container behavior Bare-metal behavior
loxilb_system_cpu_utilization_percent Whole Gateway cgroup/container CPU usage as a percentage of its allowed CPU cores Whole host CPU usage
loxilb_host_cpu_utilization_percent Whole host CPU usage Whole host CPU usage

The system gauge is not necessarily the LoxiLB process alone: helper processes in the same cgroup contribute. An explicit container CPU quota defines the allowance; otherwise available/affinity-constrained cores are used. The value is clamped to 0–100 percent.

If a container is detected but cgroup v1/v2 accounting is unreadable, the system gauge falls back to host /proc/stat. On bare metal the two gauges are therefore expected to be equal.

Use both gauges together:

loxilb_system_cpu_utilization_percent
loxilb_host_cpu_utilization_percent
  • Scoped high, host moderate: Gateway container/cgroup is near its allowance.
  • Scoped low, host high: another host workload may be causing contention.
  • Equal values: bare metal or cgroup fallback; confirm the startup log's CPU accounting source before attributing pressure.

AI access and request metrics

Metric Type Labels Meaning
loxilb_ai_requests_total Counter model, tenant, status Completed SSE inference streams by HTTP status; non-SSE requests are not counted here
loxilb_ai_request_duration_seconds Histogram model, tenant Duration recorded when an SSE inference stream completes; non-SSE requests are not observed
loxilb_ai_active_streams Gauge model Active SSE streams
loxilb_ai_rate_limit_hits_total Counter tenant, reason RPS and token-quota denials
loxilb_ai_model_not_allowed_total Counter model, tenant Model authorization denials

Denials are recorded at the decision point. Do not assume a generic completed request query includes every pre-dispatch denial; use the dedicated denial counters when alerting on 403 and 429.

Token quota metrics

Metric Type Meaning
loxilb_ai_tokens_consumed_total{model,tenant,kind} Counter Tokens charged; kind is prompt or completion
loxilb_ai_tokens_estimated_total{model,tenant} Counter Tokens charged from the estimate path
loxilb_ai_tokens_missing_total{model,tenant} Counter Completed responses without readable usage
loxilb_ai_token_quota_denied_total{tenant} Counter Requests denied by token quota
loxilb_ai_token_quota_utilization{tenant} Gauge Aggregate spent fraction after continuous refill
loxilb_ai_token_quota_limit_tokens{tenant} Gauge Aggregate TPM limit
loxilb_ai_token_quota_model_utilization{tenant,model} Gauge Model-specific spent fraction
loxilb_ai_token_quota_model_limit_tokens{tenant,model} Gauge Model-specific TPM limit
loxilb_ai_token_quota_cold_open_total Counter Quota service began with empty state because no peers were available, peer warmup was disabled, or peer warmup timed out

Utilization can exceed 1 while a completed response has created post-hoc debt. It decays as the smooth bucket refills. Aggregate and model gauges are separate admission gates and must not be added together.

Useful queries:

# Tokens charged per second, split by count source kind
sum by (kind) (rate(loxilb_ai_tokens_consumed_total[5m]))

# Estimated-accounting share; clamp avoids division by zero
sum(rate(loxilb_ai_tokens_estimated_total[5m]))
/
clamp_min(sum(rate(loxilb_ai_tokens_consumed_total[5m])), 1)

# Remaining aggregate headroom in tokens
loxilb_ai_token_quota_limit_tokens
* (1 - loxilb_ai_token_quota_utilization)

Fullproxy QoS metrics

The loxilb_proxy_qos_* family exists only for an actively shaped fullproxy service. Every series uses vip, port, proto, and direction labels; direction is upload or download.

Metric Type Unit
loxilb_proxy_qos_bytes_passed_total Counter Plaintext payload bytes
loxilb_proxy_qos_bytes_delayed_total Counter Payload bytes that waited
loxilb_proxy_qos_parks_total Counter Pause events
loxilb_proxy_qos_park_seconds_total Counter Seconds for parks that resumed
loxilb_proxy_qos_parked_connections Gauge Connections currently paused
loxilb_proxy_qos_tokens_bytes Gauge Bucket level in bytes
loxilb_proxy_qos_cir_bytes_per_second Gauge Committed rate in bytes/s
loxilb_proxy_qos_cbs_bytes Gauge Burst depth in bytes

The policy API configures Mbps, while the shaper CIR metric exports bytes/s. For example, 16 Mbps is 2,000,000 bytes/s. Do not multiply the metric by eight unless the panel is deliberately converting it to bits/s and labels the result accordingly.

# Payload throughput in bytes/s
sum by (vip, port, direction) (
  rate(loxilb_proxy_qos_bytes_passed_total[5m])
)

# Fraction of passed payload that encountered shaping
sum by (vip, port, direction) (
  rate(loxilb_proxy_qos_bytes_delayed_total[5m])
)
/
clamp_min(
  sum by (vip, port, direction) (
    rate(loxilb_proxy_qos_bytes_passed_total[5m])
  ), 1
)

Detached services disappear from this metric family after the collection refresh. A frozen series after detach is not the expected representation.

Engine and P/D metrics

Metric Meaning
loxilb_ai_engine_info{service,engine} Engine identity currently emitted only for llama.cpp rules
loxilb_ai_llamacpp_probe_warnings_total{service,kind} llama.cpp /props findings such as model/build/slot mismatch, sleeping, or unanswered
loxilb_pd_sg_prefill_abort_decode_total SGLang prefill failure aborted the decode leg
loxilb_pd_sg_decode_close_drain_total SGLang decode failure closed the prefill drain leg
loxilb_pd_sg_room_retry_total SGLang pair retried with a new bootstrap room
loxilb_pd_sg_prefill_reject_relay_total SGLang prefill 4xx relayed and decode aborted
loxilb_pd_sg_oversize_reject_total SGLang request rejected because bootstrap injection was impossible
loxilb_pd_trt_ctx_early_exit_total TensorRT-LLM context stage completed the request without generation stage
loxilb_pd_cb_flips_total All P/D circuit-breaker state transitions; correlate a rate increase with endpoint health and status
loxilb_pd_cb_proactive_heal_total Open-to-half-open transitions initiated by the periodic health pass
loxilb_pd_connect_failover_total Prefill TCP-connect failures successfully retried on another healthy prefill endpoint
loxilb_pd_prefill_ep_died_total Prefill connections that died mid-request and returned 503
loxilb_pd_decode_ep_died_total Decode connect failures or EOF before response relay

An engine info series proves the rule identity was registered; it does not prove backend correctness or traffic success. A successful connect-failover counter also does not prove that origin HTTP 5xx responses were hidden: correlate circuit transitions and endpoint-death counters with loxilb_proxy_http_responses_by_status_total, endpoint health, logs, and the client-visible result.

Relay cache and backpressure metrics

The fullproxy relay cache is bounded per connection but not by one aggregate process-wide cap. Slow backends and many concurrent request bodies can therefore raise total cached bytes even when no single connection reaches its watermark.

Metric Meaning
loxilb_proxy_cache_bytes Relay payload bytes currently cached across all connections
loxilb_proxy_cache_bytes_max_conn Largest cache held by one connection
loxilb_proxy_cache_conns_queued Connections currently holding cached relay payload
loxilb_proxy_cache_backpressure_ratio Fraction of connections with active cache backpressure
loxilb_proxy_cache_high_water_events_total Per-connection high-water activations
loxilb_proxy_cache_drain_partial_total Partial drains caused by socket flow control
loxilb_proxy_graceful_close_total Graceful closes that drained pending cached data

The normal per-connection watermark is 12 MiB and the chunked watermark is 768 KiB. loxilb_proxy_cache_bytes_max_conn approaching the applicable value means one connection is near backpressure; rising aggregate bytes with a low maximum points to concurrency rather than a single oversized body.

# Mean cached payload per queued connection
loxilb_proxy_cache_bytes
/
clamp_min(loxilb_proxy_cache_conns_queued, 1)

# New high-water activations
rate(loxilb_proxy_cache_high_water_events_total[5m])

Configuration recovery metrics

Metric Meaning
loxilb_snapshot_total{trigger} Captures produced by manual, write-through, and pre-restore; scheduled and pre-upgrade series are precreated/reserved and remain zero because no current producer invokes those triggers
loxilb_restore_total{mode,result} Dry-run, commit, and boot restore outcomes
loxilb_restore_duration_seconds Restore pipeline duration
loxilb_last_restore_timestamp_seconds Last successful committed or boot restore
loxilb_boot_config_conflict_total Boot arbitration between snapshot and legacy files

Page immediately on a new loxilb_restore_total{result="ROLLBACK-FAILED"}. Investigate repeated conflict increments and rejected/error restore results. See Configuration Backup and Restore for the procedure.

Peer synchronization metrics

The xSync metric family exposes loss and receiver-side rejection on the sockproxy peer path:

Metric Meaning
loxilb_sockproxy_sync_overflow_total{kind} Inbound event or per-peer outbound queue overflow; events/batches use drop-oldest behavior
loxilb_sockproxy_sync_drop_total{reason} Batch dropped after retry exhaustion
loxilb_sockproxy_sync_apply_errors_total Receiver could not apply a synchronized entry
loxilb_sockproxy_sync_health_reject_total{reason} Receiver rejected an entry through its local endpoint-health gate
loxilb_sockproxy_sync_conflict_total{outcome} Active-active conflict-resolution result
loxilb_sockproxy_sync_push_latency_seconds{peer,rpc} Sender-side RPC latency
loxilb_sockproxy_sync_inflight_rpc{peer} Currently in-flight synchronization RPCs

Page on new overflow, retry-exhausted drops, or apply errors during normal load. A quiet metric does not prove that a peer is connected: correlate with peer state, network evidence, and a controlled state change. Current xSync ports do not authenticate or encrypt peers themselves; see HA and Upgrade Limitations.

OPA and optional DPU metrics

OPA watcher signals are loxilb_opa_watcher_syncs_total{status}, loxilb_opa_sync_duration_seconds, loxilb_opa_firewall_rules, and loxilb_opa_circuit_breaker_state (0 closed, 1 open, 2 half-open). The watcher is currently an experimental security integration; read its production boundary before using these signals.

DPU/DOCA metrics are registered only after a DPU plugin attaches. Core signals include doca_offload_active_flows, doca_offload_attempts_total, doca_offload_failures_total, doca_circuit_breaker_state, and doca_pipe_hw_bytes_total{pipe,direction}. Absence on a standard non-DPU image is expected. See DPU Offload Observability.

Core health metrics

Metric Type Meaning
loxilb_healthy_endpoints Gauge Endpoints passing configured health checks
loxilb_unhealthy_endpoints Gauge Endpoints failing health checks
loxilb_lb_rules Gauge Configured LB rules
loxilb_active_conntrack_entries Gauge Sampled active connection-tracking entries
loxilb_proxy_http_ttfb_seconds Histogram Fullproxy time to first byte

Some metric families are created only after the corresponding feature or traffic path is used. Send one safe warm-up request before declaring a series missing.

Verify end to end

curl --fail-with-body --silent --show-error "$CONTROL_API/metrics" \
  | grep -E '^loxilb_(system_cpu|host_cpu|healthy_endpoints|lb_rules)'

Then verify Prometheus:

up{job="loxilb-inference-gateway"}

The result should be 1. Open the provisioned dashboards and compare one panel with the raw series before relying on its units or aggregation.

Troubleshooting

Symptom Likely cause Action
/metrics returns 503 Metrics are disabled Enable through /config/metrics and retry
/metrics returns 401 A proxy or deployment policy is adding auth Check the actual request path; current Gateway route itself is auth-exempt
Expected series is absent Feature unused or lazy registration Generate one safe request and confirm feature configuration
System CPU looks like host CPU in a container Cgroup accounting unreadable Check CPU-source startup log and cgroup mounts/permissions
Host CPU high, scoped CPU low Unrelated host workload Inspect host processes and scheduling pressure before restarting Gateway
Quota usage seems doubled Aggregate and model gauges were summed Display them as separate gates
QoS rate is eight times smaller than API number Bits-versus-bytes mismatch API is Mbps; shaper metric is bytes/s
llama.cpp warning rate increases Fleet consistency or sleeping endpoint Inspect kind and compare endpoint /props safely
Aggregate relay cache rises while max-connection cache stays low Many concurrent slow drains Correlate queued connections, backpressure ratio, backend latency, and memory
Restore result is ROLLBACK-FAILED Commit and automatic rollback both failed Isolate the node and follow the recovery procedure
xSync overflow or drop counter increases Slow/unreachable peer or event pressure Restrict promotion, inspect peer reachability and push latency, and verify state before serving
DPU metrics are absent No plugin attached or non-DPU build Verify the immutable build and plugin state before treating it as a scrape fault

Security and cleanup

Use low-cardinality, non-secret labels. Restrict Prometheus and Grafana access, change default credentials, validate TLS, and apply retention appropriate for tenant activity metadata. Remove temporary header files after management calls:

rm -f ./control-plane.headers
unset CONTROL_PLANE_TOKEN