Skip to content

Latest commit

 

History

History
586 lines (440 loc) · 13.2 KB

File metadata and controls

586 lines (440 loc) · 13.2 KB

Metrics & Observability Guide

Complete guide to monitoring your LLM orchestrator with Prometheus and Grafana.

Overview

The orchestrator now includes production-grade Prometheus metrics for:

  • Request throughput and latency
  • Queue depths and backpressure
  • Error rates
  • Token generation stats

Quick Start

1. Run with Metrics (5 minutes)

# Terminal 1: Start orchestrator with metrics server
cargo run --example metrics_demo --features metrics-server

Visit:

2. Set up Prometheus (10 minutes)

Download Prometheus:

Start Prometheus:

.\prometheus.exe --config.file=prometheus.yml --web.listen-address=:9091

Visit: 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"}

3. Set up Grafana (15 minutes)

Download Grafana:

Add Prometheus data source:

  1. Configuration → Data Sources → Add data source
  2. Select Prometheus
  3. URL: http://localhost:9091
  4. Save & Test

Import dashboard:

  1. Dashboards → Import
  2. Upload grafana-dashboard.json (from this repo)
  3. Select Prometheus data source
  4. Import

You now have a complete monitoring dashboard!


Available Metrics

Counters (continuously increasing)

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

Gauges (current value)

Metric Labels Description
orchestrator_queue_depth stage Current items in queue

Histograms (distribution)

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

Using Metrics in Code

Basic Recording

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);

Gather Metrics Programmatically

// 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);

Start Metrics Server

#[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();
}

Prometheus Queries

Request Rate

# 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)

Latency

# 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])

Queue Depth

# 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])

Backpressure

# 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

Errors

# 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

# 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)

Alerting Rules

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"

Grafana Dashboard

The included dashboard (grafana-dashboard.json) provides:

Overview Panels

  • Request Rate - Requests/sec by stage
  • Stage Latency - p95 latency gauges
  • Latency Percentiles - p50, p95, p99 over time

Performance Panels

  • Queue Depth - Current backlog per stage
  • Backpressure - Shed rate (bar chart)

Summary Panels

  • Total Requests - Counter
  • Total Errors - Counter with threshold
  • Total Shed - Counter with threshold
  • Total Tokens - Counter

Customization

Add new panel:

  1. Click "+ Add panel"
  2. Select metric from dropdown
  3. Configure visualization
  4. Save

Modify thresholds:

  1. Edit panel → Field tab
  2. Adjust threshold values
  3. Save

Set alerts:

  1. Edit panel → Alert tab
  2. Create alert rule
  3. Configure notification channel

Docker Deployment

docker-compose.yml

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

Best Practices

1. Set Appropriate Scrape Intervals

# High-traffic production
scrape_interval: 15s

# Development
scrape_interval: 5s

# Low-traffic
scrape_interval: 60s

2. Configure Retention

storage:
  tsdb:
    retention:
      time: 15d      # Keep 15 days
      size: 10GB     # Or max 10GB

3. Use Labels Effectively

// Good: Use labels for dimensions
metrics::record_throughput("inference", 1);

// Bad: Create separate metrics per dimension
// (Don't do this - use labels instead)

4. Monitor What Matters

Key metrics to watch:

  • p95/p99 latency (not just average)
  • Error rate (percentage, not just count)
  • Queue depth trends
  • Shed rate (backpressure indicator)

5. Set Up Alerts

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)

Troubleshooting

Metrics server won't start

Check if port is in use:

netstat -ano | findstr :9090

Use different port:

metrics_server::start_server("0.0.0.0:9091").await

Prometheus can't scrape

Test metrics endpoint:

curl http://localhost:9090/metrics

Check Prometheus targets: Visit: http://localhost:9091/targets

Common issues:

  • Firewall blocking port 9090
  • Orchestrator not running
  • Wrong target address in prometheus.yml

Grafana shows no data

Check data source:

  1. Configuration → Data Sources
  2. Test connection to Prometheus
  3. 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

High memory usage

Reduce retention:

storage:
  tsdb:
    retention:
      time: 7d
      size: 5GB

Reduce cardinality:

  • Avoid high-cardinality labels (like session IDs)
  • Use fixed label values
  • Aggregate where possible

Performance Impact

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

Next Steps

  1. Set up alerting - Configure Alertmanager
  2. Add custom metrics - Track domain-specific KPIs
  3. Integrate with logging - Correlate metrics with traces
  4. Set up dashboards - Create team-specific views
  5. Automate deployments - Use Docker/Kubernetes

For production deployments, see:


Example Output

/metrics endpoint

# 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