Complete guide to monitoring your LLM orchestrator with Prometheus and Grafana.
The orchestrator now includes production-grade Prometheus metrics for:
- Request throughput and latency
- Queue depths and backpressure
- Error rates
- Token generation stats
# Terminal 1: Start orchestrator with metrics server
cargo run --example metrics_demo --features metrics-serverVisit:
- Metrics: http://localhost:9090/metrics
- Health: http://localhost:9090/health
Download Prometheus:
- Windows: https://prometheus.io/download/
- Extract and place
prometheus.yml(from this repo) in the same folder
Start Prometheus:
.\prometheus.exe --config.file=prometheus.yml --web.listen-address=:9091Visit: http://localhost:9091
Query examples:
# Request rate per stage
rate(orchestrator_requests_total[1m])
# p95 latency
histogram_quantile(0.95, rate(orchestrator_stage_duration_seconds_bucket[5m]))
# Queue depth
orchestrator_queue_depth{stage="inference"}
Download Grafana:
- Windows: https://grafana.com/grafana/download?platform=windows
- Start Grafana (default: http://localhost:3000)
- Default login: admin/admin
Add Prometheus data source:
- Configuration → Data Sources → Add data source
- Select Prometheus
- URL:
http://localhost:9091 - Save & Test
Import dashboard:
- Dashboards → Import
- Upload
grafana-dashboard.json(from this repo) - Select Prometheus data source
- Import
You now have a complete monitoring dashboard!
| Metric | Labels | Description |
|---|---|---|
orchestrator_requests_total |
stage |
Total requests processed |
orchestrator_requests_shed_total |
stage |
Requests dropped (backpressure) |
orchestrator_errors_total |
stage, error_type |
Total errors |
orchestrator_tokens_generated_total |
worker_type |
Tokens generated by model |
| Metric | Labels | Description |
|---|---|---|
orchestrator_queue_depth |
stage |
Current items in queue |
| Metric | Labels | Buckets | Description |
|---|---|---|---|
orchestrator_stage_duration_seconds |
stage |
1ms - 10s | Stage processing time |
orchestrator_request_duration_seconds |
session |
1ms - 10s | End-to-end latency |
use tokio_prompt_orchestrator::metrics;
use std::time::{Duration, Instant};
// Record throughput
metrics::record_throughput("inference", 1);
// Record latency
let start = Instant::now();
// ... do work ...
metrics::record_stage_latency("inference", start.elapsed());
// Record queue depth
metrics::record_queue_depth("inference", tx.capacity());
// Record errors
metrics::record_error("inference", "timeout");
// Record tokens
metrics::record_tokens_generated("openai", 150);// Get Prometheus text format
let metrics_text = tokio_prompt_orchestrator::metrics::gather_metrics();
println!("{}", metrics_text);
// Get summary for logging
let summary = tokio_prompt_orchestrator::metrics::get_metrics_summary();
println!("Requests: {:?}", summary.requests_total);
println!("Shed: {:?}", summary.requests_shed);
println!("Errors: {:?}", summary.errors_total);#[tokio::main]
async fn main() {
// Start metrics server (port 9090)
let metrics_handle = tokio::spawn(
tokio_prompt_orchestrator::metrics_server::start_server("0.0.0.0:9090")
);
// Your application...
// Shutdown
metrics_handle.abort();
}# Requests per second by stage
rate(orchestrator_requests_total[1m])
# Total request rate
sum(rate(orchestrator_requests_total[1m]))
# Requests per stage (table)
sum by (stage) (orchestrator_requests_total)
# p50 latency by stage
histogram_quantile(0.50, rate(orchestrator_stage_duration_seconds_bucket[5m]))
# p95 latency
histogram_quantile(0.95, rate(orchestrator_stage_duration_seconds_bucket[5m]))
# p99 latency
histogram_quantile(0.99, rate(orchestrator_stage_duration_seconds_bucket[5m]))
# Average latency
rate(orchestrator_stage_duration_seconds_sum[5m])
/ rate(orchestrator_stage_duration_seconds_count[5m])
# Current queue depth
orchestrator_queue_depth
# Max queue depth in last 5 minutes
max_over_time(orchestrator_queue_depth[5m])
# Average queue depth
avg_over_time(orchestrator_queue_depth[5m])
# Shed rate (requests/sec)
rate(orchestrator_requests_shed_total[1m])
# Total shed count
sum(orchestrator_requests_shed_total)
# Shed percentage
sum(rate(orchestrator_requests_shed_total[5m]))
/ sum(rate(orchestrator_requests_total[5m])) * 100
# Error rate
rate(orchestrator_errors_total[1m])
# Errors by type
sum by (error_type) (orchestrator_errors_total)
# Error percentage
sum(rate(orchestrator_errors_total[5m]))
/ sum(rate(orchestrator_requests_total[5m])) * 100
# Tokens per second
rate(orchestrator_tokens_generated_total[1m])
# Total tokens
sum(orchestrator_tokens_generated_total)
# Tokens by worker type
sum by (worker_type) (orchestrator_tokens_generated_total)
Create orchestrator_alerts.yml:
groups:
- name: orchestrator
interval: 30s
rules:
# High error rate
- alert: HighErrorRate
expr: |
sum(rate(orchestrator_errors_total[5m]))
/ sum(rate(orchestrator_requests_total[5m])) > 0.05
for: 2m
labels:
severity: warning
annotations:
summary: "High error rate detected"
description: "Error rate is {{ $value | humanizePercentage }}"
# High latency
- alert: HighLatency
expr: |
histogram_quantile(0.95,
rate(orchestrator_stage_duration_seconds_bucket{stage="inference"}[5m])
) > 5
for: 5m
labels:
severity: warning
annotations:
summary: "High inference latency"
description: "p95 latency is {{ $value }}s"
# High backpressure
- alert: HighBackpressure
expr: |
sum(rate(orchestrator_requests_shed_total[5m]))
/ sum(rate(orchestrator_requests_total[5m])) > 0.10
for: 5m
labels:
severity: critical
annotations:
summary: "High backpressure - shedding requests"
description: "Shedding {{ $value | humanizePercentage }} of requests"
# Queue depth high
- alert: QueueDepthHigh
expr: orchestrator_queue_depth{stage="inference"} > 900
for: 2m
labels:
severity: warning
annotations:
summary: "Queue depth approaching limit"
description: "Queue at {{ $value }} (limit: 1024)"
# Service down
- alert: OrchestratorDown
expr: up{job="orchestrator"} == 0
for: 1m
labels:
severity: critical
annotations:
summary: "Orchestrator is down"
description: "Orchestrator has been down for {{ $value }}m"Add to prometheus.yml:
rule_files:
- "orchestrator_alerts.yml"The included dashboard (grafana-dashboard.json) provides:
- Request Rate - Requests/sec by stage
- Stage Latency - p95 latency gauges
- Latency Percentiles - p50, p95, p99 over time
- Queue Depth - Current backlog per stage
- Backpressure - Shed rate (bar chart)
- Total Requests - Counter
- Total Errors - Counter with threshold
- Total Shed - Counter with threshold
- Total Tokens - Counter
Add new panel:
- Click "+ Add panel"
- Select metric from dropdown
- Configure visualization
- Save
Modify thresholds:
- Edit panel → Field tab
- Adjust threshold values
- Save
Set alerts:
- Edit panel → Alert tab
- Create alert rule
- Configure notification channel
version: '3.8'
services:
orchestrator:
build: .
ports:
- "9090:9090" # Metrics
environment:
- RUST_LOG=info
- OPENAI_API_KEY=${OPENAI_API_KEY}
prometheus:
image: prom/prometheus:latest
ports:
- "9091:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
- ./grafana-dashboard.json:/etc/grafana/provisioning/dashboards/orchestrator.json
- grafana-data:/var/lib/grafana
depends_on:
- prometheus
volumes:
prometheus-data:
grafana-data:Run:
docker-compose up -d# High-traffic production
scrape_interval: 15s
# Development
scrape_interval: 5s
# Low-traffic
scrape_interval: 60sstorage:
tsdb:
retention:
time: 15d # Keep 15 days
size: 10GB # Or max 10GB// Good: Use labels for dimensions
metrics::record_throughput("inference", 1);
// Bad: Create separate metrics per dimension
// (Don't do this - use labels instead)Key metrics to watch:
- p95/p99 latency (not just average)
- Error rate (percentage, not just count)
- Queue depth trends
- Shed rate (backpressure indicator)
Start with these critical alerts:
- Service down (1 minute)
- High error rate (>5% for 2 minutes)
- High latency (p95 >5s for 5 minutes)
- High backpressure (>10% shed for 5 minutes)
Check if port is in use:
netstat -ano | findstr :9090Use different port:
metrics_server::start_server("0.0.0.0:9091").awaitTest metrics endpoint:
curl http://localhost:9090/metricsCheck Prometheus targets: Visit: http://localhost:9091/targets
Common issues:
- Firewall blocking port 9090
- Orchestrator not running
- Wrong target address in prometheus.yml
Check data source:
- Configuration → Data Sources
- Test connection to Prometheus
- Verify URL:
http://localhost:9091
Check time range:
- Set to "Last 5 minutes"
- Ensure orchestrator is running and generating metrics
Check query:
- Use Explore tab to test queries
- Verify metric names match
Reduce retention:
storage:
tsdb:
retention:
time: 7d
size: 5GBReduce cardinality:
- Avoid high-cardinality labels (like session IDs)
- Use fixed label values
- Aggregate where possible
Metrics collection is designed to be lightweight:
- CPU overhead: <1% (Prometheus client is very efficient)
- Memory overhead: ~10MB baseline + 1KB per unique metric
- Latency impact: <100μs per recorded metric
For high-throughput scenarios:
- Metrics are recorded with atomic operations (lock-free)
- Histograms use pre-allocated buckets
- HTTP server runs on separate runtime
- Set up alerting - Configure Alertmanager
- Add custom metrics - Track domain-specific KPIs
- Integrate with logging - Correlate metrics with traces
- Set up dashboards - Create team-specific views
- Automate deployments - Use Docker/Kubernetes
For production deployments, see:
# HELP orchestrator_requests_total Total number of requests processed
# TYPE orchestrator_requests_total counter
orchestrator_requests_total{stage="rag"} 1523
orchestrator_requests_total{stage="assemble"} 1523
orchestrator_requests_total{stage="inference"} 1520
orchestrator_requests_total{stage="post"} 1520
orchestrator_requests_total{stage="stream"} 1520
# HELP orchestrator_stage_duration_seconds Stage processing duration in seconds
# TYPE orchestrator_stage_duration_seconds histogram
orchestrator_stage_duration_seconds_bucket{stage="inference",le="0.1"} 1200
orchestrator_stage_duration_seconds_bucket{stage="inference",le="0.5"} 1480
orchestrator_stage_duration_seconds_bucket{stage="inference",le="1"} 1520
orchestrator_stage_duration_seconds_sum{stage="inference"} 152.3
orchestrator_stage_duration_seconds_count{stage="inference"} 1520
# HELP orchestrator_queue_depth Current queue depth
# TYPE orchestrator_queue_depth gauge
orchestrator_queue_depth{stage="rag"} 0
orchestrator_queue_depth{stage="inference"} 23
orchestrator_queue_depth{stage="post"} 5