Monitoring & Logging¶
Each Ray node exposes Prometheus metrics on one port via Ray's metrics agent. When enabled, all of a node's metrics — Ray cluster, Ray Serve, vLLM engine, and custom Modelship metrics — are available on that node's scrape endpoint.
Logging¶
Modelship uses a centralized logging system with structured output and request correlation. All application logs go through the modelship.* logger hierarchy, separate from library logs (Ray, vLLM, etc.).
Configuration¶
| Env Var | Default | Description |
|---|---|---|
MSHIP_LOG_LEVEL |
INFO |
App log level. Set to TRACE for request/response payloads, DEBUG for detailed diagnostics. Library logs are silent unless it is DEBUG or TRACE, which set them to DEBUG. |
MSHIP_LOG_FORMAT |
text |
text for human-readable output, json for structured JSON lines (for log aggregation with ELK/Loki/Splunk). |
MSHIP_LOG_TARGET |
console |
Log target. console writes to stderr; syslog URIs ship logs to a remote syslog server (see below). |
OTEL_EXPORTER_OTLP_ENDPOINT |
— | When set, logs are also exported to an OpenTelemetry collector via OTLP (see below). |
MSHIP_LOG_FORMAT (--log-format) is set once, on mship start, for every actor on the cluster. The level, target and OTLP endpoint (--log-level, --log-target, --otel-endpoint) belong to a node: set them on mship start or mship join, and every actor on that node uses them, library levels included. mship deploy and stop read these env vars only for their own output. Ray Serve's logs follow the node too, with two exceptions that use the head's level: Serve's HTTP proxy on every node, and a replica's first startup lines, logged before modelship's code runs in it.
Log Levels¶
Library logs (Ray, vLLM, transformers) are silent unless the level is DEBUG or TRACE:
| Level | App logs (modelship.*) |
Library logs |
|---|---|---|
TRACE |
Request/response payloads (audio bytes, transcription text, chat messages, etc.) | DEBUG |
DEBUG |
Detailed diagnostics, per-chunk details | DEBUG |
INFO (default) |
Startup, deployment, request summaries | silent |
WARNING |
Warnings only | silent |
Request Correlation¶
Every API request is assigned a unique request ID that appears in all log lines for that request — both in the API gateway process and in the model deployment actor. This allows tracing a request end-to-end across Ray actor boundaries.
Text format example:
[2025-04-09 14:06:54] INFO modelship.api [a1b2c3d4] | chat_completion model=llama messages=3 stream=True max_tokens=512
JSON format example:
{"timestamp": "2025-04-09T14:06:54", "level": "INFO", "logger": "modelship.api", "message": "chat_completion model=llama messages=3 stream=True max_tokens=512", "request_id": "a1b2c3d4", "pid": 12345}
Logger Names¶
| Logger | Scope |
|---|---|
modelship.startup |
Application initialization and shutdown |
modelship.api |
API gateway endpoints |
modelship.infer |
Base inference layer |
modelship.infer.deployment |
Ray Serve model deployment actor |
modelship.infer.vllm |
vLLM inference backend |
modelship.infer.diffusers |
Diffusers inference backend |
modelship.infer.diffusers.image |
Diffusers image generation |
Syslog¶
Ship logs to a remote syslog server instead of stderr. Useful for centralized logging on bare-metal or Unraid setups without extra infrastructure.
# UDP (default)
mship start --log-target syslog://192.168.1.50:514
# TCP (reliable delivery)
mship start --log-target syslog+tcp://192.168.1.50:514
# Via environment variable
MSHIP_LOG_TARGET=syslog://192.168.1.50:514 mship start
Supported URI formats:
| URI | Protocol | Notes |
|---|---|---|
syslog://host:port |
UDP | Default, fire-and-forget |
syslog+tcp://host:port |
TCP | Reliable delivery |
syslog://host |
UDP | Port defaults to 514 |
The syslog target replaces the console handler — logs go to the syslog server only. The --log-format setting still applies (text or JSON).
OpenTelemetry¶
Export logs to an OpenTelemetry collector via OTLP. Unlike syslog, OTel is additive — logs still go to the console (or syslog) handler, and are also shipped to the collector.
First, install the optional dependencies:
Then configure the endpoint:
# Via CLI
mship start --otel-endpoint http://collector:4317
# Via environment variable
OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4317 mship start
When OTel is enabled:
- Logs are exported via BatchLogRecordProcessor with OTLPLogExporter (gRPC)
- The service name is set to modelship
- RAY_TRACING_ENABLED=1 is set automatically so Ray workers also export traces
- HTTPS endpoints are detected from the URI scheme; all others use insecure connections
If the opentelemetry-sdk and opentelemetry-exporter-otlp packages are not installed, a warning is logged and OTel is skipped.
Architecture¶
Prometheus ──scrape──> Ray Metrics Agent (:8079)
|
|-- ray_node_* Ray cluster: GPU, CPU, memory
|-- ray_serve_* Ray Serve: HTTP requests, latency, replicas
|-- ray_vllm_* vLLM engine: KV cache, TTFT, tokens, queue
|-- ray_modelship_* Custom: per-model latency, errors, load time
Note: All metrics are prefixed with
ray_by Ray's metrics agent. vLLM metric names are also sanitized (:→_), so e.g. the vLLM-nativevllm:kv_cache_usage_percbecomesray_vllm_kv_cache_usage_perc.
Enabling Metrics¶
Metrics are enabled by default. Set MSHIP_METRICS=false to disable:
docker run --rm --shm-size=8g --gpus all \
-e HF_TOKEN=your_token \
-v ./models.yaml:/models.yaml \
-v ./models-cache:/.cache \
-p 8000:8000 -p 8079:8079 -p 8265:8265 \
ghcr.io/modelship-ai/modelship:latest-cuda start --config /models.yaml --ray-dashboard-host 0.0.0.0 --enable-ray-auth
The dashboard always starts and binds
127.0.0.1by default —--ray-dashboard-host 0.0.0.0above is what makes the published8265port actually reachable. Only do this on a trusted/private network (see troubleshooting.md).--enable-ray-authmakes the now-reachable dashboard require a bearer token — retrieve it withdocker exec <container> cat /home/modelship/.ray/auth_token, or set your own with-e MSHIP_RAY_AUTH_TOKEN.
| Env Var | Default | Description |
|---|---|---|
MSHIP_METRICS |
true |
Master toggle for modelship's own metrics on the whole cluster (--no-metrics on mship start); also pins the Ray metrics export port. |
MSHIP_METRICS_PORT |
8079 on start, random on join |
This node's metrics port (--metrics-port). |
MSHIP_METRICS=false turns modelship's own metrics into no-ops and leaves port 8079 free. Ray's
per-node exporter has no off switch — it keeps serving Ray's system metrics on a random port.
Connecting to Prometheus¶
Add Modelship as a scrape target in your prometheus.yml:
scrape_configs:
- job_name: modelship
scrape_interval: 15s
static_configs:
- targets: ["<modelship-host>:8079"]
In a multi-node cluster every node serves only its own metrics, so Prometheus scrapes each node. The head lists every node's address and port in a service discovery file, which Prometheus can read when it runs on the head's host:
scrape_configs:
- job_name: modelship
file_sd_configs:
- files: ["/tmp/ray/prom_metrics_service_discovery.json"]
Otherwise pin --metrics-port on every node and list the nodes as static targets. The Helm chart pins it, and its PodMonitor scrapes each pod's metrics port.
The alert rules expect the scrape job to be named modelship. The examples above use it, and the chart's PodMonitor sets it on every target whatever the release or nameOverride.
Connecting to Grafana¶
A pre-built Grafana dashboard is included at docs/grafana-dashboard.json.
To import it:
- Open Grafana and go to Dashboards > Import
- Upload
grafana-dashboard.jsonor paste its contents - Select your Prometheus datasource when prompted
At the top the dashboard has Gateway, Model, and Node dropdowns. The Gateway dropdown filters every ray_modelship_* panel to a single gateway (or All); the vLLM/Ray-Serve/GPU rows stay cluster-wide since those metrics aren't per-gateway (see The gateway dimension).
The dashboard has 9 rows:
| Row | What it shows | Metric sources |
|---|---|---|
| Overview | Request rate, error rate, in-flight requests, models loaded, client disconnects | ray_modelship_* |
| Latency | Gateway P50/P95/P99, per-model latency, per-usecase latency (generate, TTS, image, STT, embed) | ray_modelship_* |
| vLLM Engine | KV cache usage, TTFT, inter-token latency, token throughput, queue depth, preemptions, prefix cache hit rate | ray_vllm_* |
| GPU & System | GPU utilization, GPU memory, CPU, system memory (cluster aggregate) | ray_node_* |
| Ray Serve | Health check latency, request count, deployment processing latency, HTTP request latency | ray_serve_* |
| Operational | Model load time, load failures, resource cleanup errors, streaming chunks/s | ray_modelship_* |
| Alerts | Error rate %, KV cache usage, queue depth, TTFT P99, client disconnects, preemptions, GPU memory | ray_modelship_*, ray_vllm_*, ray_node_* |
| Cluster / HA | Model app status, gateway reconciles, Serve requests per model, gateway replicas behind the routing generation, gateway watch errors, state-store latency/errors, deploy duration & model changes | ray_modelship_*, ray_serve_* |
| Per-Node Resources | GPU utilization/memory, CPU, system memory broken out by node (filtered by the Node dropdown) | ray_node_* |
Deploying via the Helm chart: set
grafanaDashboard.enabled=trueto ship this dashboard as a ConfigMap the Grafana sidecar auto-imports (andprometheusRule.enabled=truefor the alert rules below) — no manual import needed. See the chart README.
Alerting¶
A standalone Prometheus alerting rules file is included at docs/prometheus-alerts.yml. The Grafana dashboard also has a dedicated Alerts row with threshold lines on the key panels.
Importing Alert Rules¶
Add the rules file to your Prometheus config:
Then reload Prometheus (kill -HUP <pid> or POST /-/reload if --web.enable-lifecycle is set).
Alert Reference¶
Critical (page-worthy)¶
| Alert | Condition | For | Description |
|---|---|---|---|
ModelshipHighErrorRate |
Error rate > 5% of traffic | 5m | Significant portion of requests are failing |
ModelshipHTTP5xxRatio |
5xx > 5% of a gateway's HTTP requests, /health and /readyz excluded |
5m | Gateway is answering server errors |
ModelshipNoModelsLoaded |
models_loaded == 0 |
2m | Server is running but cannot serve requests |
ModelshipKVCacheExhausted |
KV cache usage > 95% | 5m | Requests will queue or be preempted |
ModelshipAppUnhealthy |
ray_serve_application_status == 2 (UNHEALTHY) |
5m | A Serve application has an unhealthy deployment |
ModelshipTargetDown |
up{job="modelship"} == 0 |
5m | Prometheus cannot scrape a node |
ModelshipStateStoreErrors |
Any state-store op errors | 5m | Durable HA state failing — self-heal at risk |
ModelshipDeployJobFailed |
kube_job_failed (condition true) > 0 |
0m | Deploy Job failed; last upgrade may not be applied (needs kube-state-metrics) |
Warning (investigate)¶
| Alert | Condition | For | Description |
|---|---|---|---|
ModelshipHighP99Latency |
Gateway P99 > 30s | 5m | End-to-end latency is very high |
ModelshipModelHighErrorRate |
One model's error rate > 5%, above 0.05 requests/s | 5m | A model is failing while the cluster-wide rate may look fine |
ModelshipHighQueueDepth |
Waiting requests > 10 | 5m | vLLM engine is falling behind |
ModelshipPreemptions |
Preemption rate > 0 | 5m | GPU memory pressure causing request eviction |
ModelshipClientDisconnects |
Disconnect rate > 1/min | 5m | Clients timing out or dropping connections |
ModelshipGPUMemoryPressure |
Available GPU memory < 1 GB | 5m | GPU is nearly out of memory |
ModelshipHighTTFT |
TTFT P99 > 5s | 5m | Users waiting too long for first token |
ModelshipDeployStuck |
ray_serve_application_status == 5 (DEPLOYING) |
30m | A Serve application is waiting for capacity or a slow model load |
ModelshipGatewayRoutingDivergence |
gateway_replicas_behind > 0 |
10m | A gateway replica is routing from a stale table |
ModelshipRayWorkerDown |
Ray worker pod not running or crash-looping | 5m | Cluster capacity degraded (needs kube-state-metrics); a worker hosting no replica is NotReady by design |
Tuning Thresholds¶
All thresholds are starting points. Adjust based on your deployment:
- Error rate: 5% is aggressive — if you run small models that occasionally OOM, raise to 10%.
- P99 latency: 30s works for chat completions with long outputs. For embeddings or TTS, consider lowering to 5-10s by adding per-endpoint rules.
- Queue depth: 10 assumes a single vLLM instance. Scale proportionally with replicas.
- KV cache: 95% is the danger zone. If you use prefix caching heavily, 90% may be more appropriate.
- TTFT: 5s is generous. For interactive chat, consider 2-3s.
- GPU memory: 1 GB threshold assumes you're not running anything else on the GPU. Raise if you have shared workloads.
Health and readiness¶
Two endpoints are always available regardless of the metrics toggle.
/health — cheap liveness probe. Returns 200 as soon as the gateway is up (before model deployments finish):
/readyz — readiness + timing. Returns 200 when at least one model is expected and every expected model has a deployment the gateway routes to; 503 with the same JSON body while any model is still pending. A gateway with no models configured at all is never ready — /readyz stays 503 indefinitely, even though models_expected and models_pending are both empty (there is nothing to be "pending" on, but nothing to serve either, so it never reports ready). This matters for a genuinely model-less control-plane node (e.g. a k8s head-only install with an empty models.yaml) or an external LB/Ingress health check pointed at /readyz before any model is configured — both stay permanently unhealthy by design, not by bug. Bodies carry full state so a single poll tells you what's loaded, what's outstanding, and how long each model took to come up:
curl http://localhost:8000/modelship/readyz
# 200 when ready:
# {
# "status": "ok",
# "ready": true,
# "uptime_s": 420.5,
# "time_to_ready_s": 215.3,
# "models_loaded": ["kokoro", "llm", "nomic-embed", "whisper"],
# "models_expected": ["llm", "kokoro", "whisper", "nomic-embed"],
# "models_pending": [],
# "model_load_times_s": {"llm": 50.2, "kokoro": 8.1, "whisper": 120.5, "nomic-embed": 36.5}
# }
Per-model timings are gateway-measured: the gap between one model becoming routable and the one before it. That matches load time only when models load one after another, as they do on a single node; the first model's entry includes any framework-level setup time preceding it.
Use /health for Kubernetes liveness probes and /readyz for readiness probes — /readyz returning 503 prevents a service from flipping traffic onto the pod before models are loaded.
Modelship Metrics Reference¶
Custom metrics are exported through Ray's metrics agent with a ray_ prefix. Per-model/per-gateway metrics use ray.serve.metrics (they're emitted inside Serve replicas); the HA control-plane metrics use ray.util.metrics (emitted by the gateway coordinator, state store, and deploy worker, which are not Serve replicas).
The gateway dimension¶
Every per-model and per-gateway metric below carries a gateway tag identifying which gateway emitted it, so multiple gateways sharing one Ray cluster stay distinguishable (the Grafana dashboard exposes a Gateway dropdown built on it). The tag is stamped automatically from MSHIP_GATEWAY_NAME — the deploy driver sets it and forwards it to every replica via runtime_env, so no call site passes it explicitly.
Cluster-scoped metrics are not per-gateway, because the thing they measure is shared cluster-wide: the state store (one shared backend), and all inherited ray_vllm_* / ray_serve_* / ray_node_* metrics (engine/cluster level). The dashboard's Gateway dropdown therefore filters the ray_modelship_* per-model panels but leaves the vLLM/GPU/Ray-Serve panels cluster-wide.
Gateway¶
| Metric | Type | Tags | Description |
|---|---|---|---|
ray_modelship_request_total |
Counter | model, endpoint, status, gateway |
Total requests by model and API method |
ray_modelship_request_duration_seconds |
Histogram | model, endpoint, gateway |
End-to-end request latency |
ray_modelship_request_errors_total |
Counter | model, endpoint, error_type, gateway |
Errors: validation_error, inference_error, stream_error, unhandled |
ray_modelship_client_disconnects_total |
Counter | model, endpoint, gateway |
Client disconnected before response completed |
ray_modelship_stream_chunks_total |
Counter | model, gateway |
Streaming chunks emitted |
Model Deployment¶
| Metric | Type | Tags | Description |
|---|---|---|---|
ray_modelship_model_load_duration_seconds |
Histogram | model, loader, gateway |
Time to initialize a model |
ray_modelship_model_load_failures_total |
Counter | model, loader, gateway |
Failed model initializations |
ray_modelship_models_loaded |
Gauge | gateway |
Number of loaded and ready models |
Inference Timing¶
| Metric | Type | Tags | Description |
|---|---|---|---|
ray_modelship_generation_duration_seconds |
Histogram | model, gateway |
Chat/text generation latency |
ray_modelship_tts_generation_duration_seconds |
Histogram | model, gateway |
Text-to-speech latency |
ray_modelship_image_generation_duration_seconds |
Histogram | model, gateway |
Image generation latency |
ray_modelship_transcription_duration_seconds |
Histogram | model, gateway |
Speech-to-text latency |
ray_modelship_embedding_duration_seconds |
Histogram | model, gateway |
Embedding latency |
Resource Cleanup¶
| Metric | Type | Tags | Description |
|---|---|---|---|
ray_modelship_resource_cleanup_errors_total |
Counter | model, component, gateway |
Errors during engine/model cleanup |
HA Control Plane¶
These cover the multi-node / HA machinery deployed by the Helm chart (gateway coordinator, pluggable state store, gateway watch loop). The first five carry a gateway tag; the rest are cluster-scoped.
| Metric | Type | Tags | Description |
|---|---|---|---|
ray_modelship_gateway_reconciles_total |
Counter | gateway |
Routing reconciles a gateway replica applied from the coordinator |
ray_modelship_gateway_watch_errors_total |
Counter | gateway |
Errors in a gateway replica's coordinator watch loop (retried) |
ray_modelship_gateway_replicas_behind |
Gauge | gateway |
Running gateway replicas not on the gateway coordinator's current routing generation |
ray_modelship_deploy_duration_seconds |
Histogram | gateway |
Wall-clock time for a deploy request to succeed |
ray_modelship_deploy_models_changed_total |
Counter | gateway, action |
Models changed by a deploy request (action: add, remove and rescale on success, fail once per failed request) |
ray_modelship_state_store_operations_total |
Counter | backend, op, result |
State-store ops (op: get/set/delete; result: ok/error) |
ray_modelship_state_store_operation_duration_seconds |
Histogram | backend, op |
State-store operation latency |
Built-in Metrics from vLLM and Ray¶
These are automatically available when MSHIP_METRICS=true — no additional configuration needed.
vLLM (ray_vllm_*)¶
vLLM metrics are routed through Ray's metrics agent via RayPrometheusStatLogger. The native vllm: prefix is sanitized to ray_vllm_.
ray_vllm_num_requests_running/ray_vllm_num_requests_waiting— queue depthray_vllm_kv_cache_usage_perc— KV cache utilization (0-1)ray_vllm_time_to_first_token_seconds— TTFT histogramray_vllm_inter_token_latency_seconds— ITL histogramray_vllm_e2e_request_latency_seconds— end-to-end latency histogramray_vllm_request_queue_time_seconds— time spent waiting in queueray_vllm_prompt_tokens_total/ray_vllm_generation_tokens_total— token throughput countersray_vllm_num_preemptions_total— memory pressure signalray_vllm_prefix_cache_hits_total/ray_vllm_prefix_cache_queries_total— cache efficiency
Full reference: vLLM Metrics Documentation
Ray Serve (ray_serve_*)¶
ray_serve_num_http_requests_total— request count by route, method, statusray_serve_http_request_latency_ms— request latency histogramray_serve_handle_request_counter_total— request count by deploymentray_serve_deployment_processing_latency_ms— per-replica processing timeray_serve_health_check_latency_ms— health check latency histogramray_serve_application_status— status of each application: 0=UNKNOWN, 1=DEPLOY_FAILED, 2=UNHEALTHY, 3=NOT_STARTED, 4=DELETING, 5=DEPLOYING, 6=RUNNING. A model's application is named<gateway>.<model>-<fingerprint>.ray_serve_replica_processing_queries— requests each replica is processingray_serve_deployment_queued_queries— requests queued at the caller (the gateway replica, for a model's application)
Full reference: Ray Serve Monitoring
Ray Cluster (ray_*)¶
ray_node_gpus_utilization— GPU utilization by deviceray_node_gram_used/ray_node_gram_available— GPU memoryray_node_cpu_utilization— CPU usageray_node_mem_used/ray_node_mem_total— system memory
Full reference: Ray Metrics