Wingsmith
Metrics

Metrics reference

Every Prometheus series the Wingsmith plane, gateway, operator and agent export, with labels and meaning.

Four components serve Prometheus text format on their own listener. To scrape them and read the fleet’s metrics in the console, see Monitoring.

ComponentEndpointNotes
Plane api:8000/metricsNo credential. Each pod reports only its own requests, so scrape every pod
Gateway:9090/metricsHANGAR_GATEWAY_METRICS_LISTEN; off disables
Operator:8080/metricsChart value operator.metrics.enabled, on by default
Agent:8081/metricsShares HANGAR_HEALTH_ADDR with the probes

The plane’s hub and reactor processes export nothing yet. No series carries request or response content, caller identity, API keys or client addresses.

Plane

SeriesTypeLabelsWhat
hangar_plane_http_requests_totalcountermethod, route, statusRequests answered. route is the matched template, such as /v1/clusters/{ref}
hangar_plane_http_request_duration_secondshistogrammethod, routeTime from the request reaching the api to the end of its response. Default client buckets, 5 ms to 10 s
hangar_plane_http_requests_in_flightgaugeRequests being answered now
hangar_plane_build_infogaugeversionAlways 1. version is the hangar-plane package version
hangar_plane_db_pool_connectionsgaugestate (checked_out, idle)Connections in the database pool. Absent until the process opens its pool
hangar_plane_db_pool_sizegaugeConfigured pool size (HANGAR_DB_POOL_SIZE). Absent until the pool opens
hangar_plane_db_pool_overflowgaugeConnections open beyond the pool size, up to HANGAR_DB_POOL_MAX_OVERFLOW. Negative while the pool is not full
process_*, python_info, python_gc_*variousProcess and interpreter series. process_* is Linux only

Requests that match no route (a 404 before routing) are not counted.

Useful queries:

# Error ratio by route over five minutes
sum by (route) (rate(hangar_plane_http_requests_total{status=~"5.."}[5m]))
  / sum by (route) (rate(hangar_plane_http_requests_total[5m]))

# p95 latency by route
histogram_quantile(0.95, sum by (route, le) (rate(hangar_plane_http_request_duration_seconds_bucket[5m])))

# Pool in use beyond its size
hangar_plane_db_pool_connections{state="checked_out"} >= on(job, instance) hangar_plane_db_pool_size

Gateway

The gateway counts every request, including ones that never reach a cluster. Request series share the labels hangar_endpoint, endpoint_name and origin. The routed series add deployment and cluster, which are empty strings when a request never got that far.

Requests

SeriesTypeExtra labelsWhat
hangar_gateway_requests_totalcounterrouted, status, outcomeRequests answered
hangar_gateway_request_duration_secondshistogramroutedTime this hop spent on a request, first byte in to last byte out. Buckets 50 ms to 300 s
hangar_gateway_ttft_secondshistogramroutedArrival to first response byte forwarded. Only requests that reached a cluster
hangar_gateway_ttft_by_input_secondshistogramcommon, input_bucketTime to first byte banded by prompt length. unknown when the request never disclosed one
hangar_gateway_requests_in_flightgaugeroutedRequests being proxied to a cluster now
hangar_gateway_upstream_replays_totalcounterroutedRequest bodies sent to a cluster again because the transport retried
hangar_gateway_upstream_upgaugerouted1 when the plane’s last table reported the deployment running. This is the plane’s observation, not a probe
hangar_gateway_tokens_totalcounterrouted, directionTokens from the upstream’s own usage object. direction is input, output or cached_input
hangar_gateway_generated_totalcounterrouted, unitImage and video output counted by the gateway: images, videos, video_seconds, video_frames, upstream_seconds

Token and generation counts are observability, not metering. Do not reconcile them against an invoice.

origin is live for callers and probe for the conformance suite. outcome is one of ten values: ok, client_error, auth_rejected, rate_limited, shed, no_upstream, upstream_error, upstream_timeout, canceled, internal_error. rate_limited and shed can both be HTTP 429 (shed can also be a 503), but only shed is a failure, so key failure rates off outcome, not status.

hangar_endpoint is _catalog for GET /v1/models and _unknown for a model the gateway does not serve.

Routing table

SeriesTypeLabelsWhat
hangar_gateway_routing_table_age_secondsgaugeSeconds since the plane last confirmed the loaded table. Grows while the plane is unreachable
hangar_gateway_routing_table_confirmed_timestamp_secondsgaugeUnix time of that confirmation
hangar_gateway_routing_table_refresh_successgauge1 if the last refresh reached the plane, 0 if not
hangar_gateway_routing_table_refreshes_totalcounterresult (applied, unchanged, error)Refresh attempts. A failed refresh keeps the loaded table
hangar_gateway_routing_table_endpointsgaugeEndpoints in the loaded table

Tunnels

SeriesTypeLabelsWhat
hangar_gateway_tunnelsgaugecluster_idLive tunnels this replica holds to a cluster
hangar_gateway_tunnel_coveragegaugecluster_id, cluster1 if this replica can reach the cluster, 0 if its routing table routes there but it cannot
hangar_gateway_tunnel_streams_activegaugecluster_idRequests open on this replica’s tunnels to a cluster
hangar_gateway_tunnel_streams_capacitygaugecluster_idStream limits the agents advertised, summed. When active meets capacity, new requests get 503 no_capacity
hangar_gateway_tunnel_dials_rejected_totalcounterreasonDials that did not become tunnels. Reasons include unauthorized, unavailable (plane unreachable), limit and throttled
hangar_gateway_tunnel_dials_duplicate_totalcounterDials that landed on this replica after the agent already covered it
hangar_gateway_tunnels_revoked_totalcountercluster_idOpen tunnels closed because the plane no longer accepts their token
hangar_gateway_tunnel_retries_totalcountercluster_id, outcomeRequests whose tunnel died under them: resent on another tunnel, written (not resent, the agent may have forwarded it) or exhausted
hangar_gateway_tunnel_certificate_expiry_timestamp_secondsgaugeUnix time the tunnel certificate loaded in memory expires. Alert as it nears now
hangar_gateway_build_infogaugeversionAlways 1

The gateway also exports the standard Go runtime and process series.

Operator

The operator registers no series of its own yet. It exports controller-runtime’s standard families. A read-only operator runs no reconciler, so the controller_runtime_* families are absent there.

SeriesLabelsWhat
controller_runtime_reconcile_totalcontroller, resultReconciles by outcome
controller_runtime_reconcile_errors_totalcontrollerReconciles that returned an error
controller_runtime_terminal_reconcile_errors_totalcontrollerErrors that will not be retried
controller_runtime_reconcile_panics_totalcontrollerReconciles that panicked
controller_runtime_reconcile_time_secondscontrollerReconcile latency (histogram)
controller_runtime_max_concurrent_reconciles, controller_runtime_active_workerscontrollerWorker capacity and use
workqueue_depth, workqueue_adds_total, workqueue_retries_totalname, controllerQueue size and churn
workqueue_queue_duration_seconds, workqueue_work_duration_secondsname, controllerTime waiting and time working (histograms)
workqueue_unfinished_work_seconds, workqueue_longest_running_processor_secondsname, controllerStuck work
rest_client_requests_totalcode, method, hostKubernetes API calls
leader_election_master_statusname1 on the replica holding the lease
go_*, process_*Go runtime and process

Hub-session health is not a metric. It is the operator’s /readyz, and the plane reports it per cluster.

Agent

SeriesTypeLabelsWhat
hangar_agent_requests_totalcounteroutcome, codeRequests that arrived over a tunnel. forwarded with a 5xx code is the frontend’s own error, frontend_unavailable is a frontend that did not answer, aborted is a response cut after it started
hangar_agent_requests_in_flightgaugeRequests being served across all tunnels
hangar_agent_request_duration_secondshistogramTunnel arrival to last response byte. Same buckets as the gateway
hangar_agent_gateways_expectedgaugeGateway replicas the gateway last reported it was running
hangar_agent_tunnelsgaugegatewayEstablished tunnels to each gateway replica
hangar_agent_gateway_upgaugegateway1 when at least one tunnel to the replica is established
hangar_agent_dial_attempts_totalcounterOutbound dials attempted
hangar_agent_dial_failures_totalcounterreason (dial, unauthorized, handshake, refused)Dials that did not produce a tunnel
hangar_agent_dial_duplicates_totalcountergatewayDials answered by a replica that already had its quota of tunnels. Not a failure
hangar_agent_tunnels_established_totalcountergatewayTunnels that completed the preface
hangar_agent_tunnels_closed_totalcountergatewayTunnels that ended
hangar_agent_tunnel_secondshistogramgatewayTunnel lifetime. Buckets 1 s to 12 h
hangar_agent_frontend_discovery_age_secondsgaugeSeconds since frontend discovery last got an answer from the API server

Coverage is complete when the number of hangar_agent_gateway_up == 1 series equals hangar_agent_gateways_expected.