|
| 1 | +/* |
| 2 | + * Copyright (c) Meta Platforms, Inc. and affiliates. |
| 3 | + * All rights reserved. |
| 4 | + * |
| 5 | + * This source code is licensed under the BSD-style license found in the |
| 6 | + * LICENSE file in the root directory of this source tree. |
| 7 | + */ |
| 8 | + |
| 9 | +//! Example showing how to use the Prometheus OTLP backend for telemetry. |
| 10 | +//! |
| 11 | +//! This example demonstrates the modern approach to Prometheus integration using |
| 12 | +//! OpenTelemetry's OTLP HTTP protocol to send metrics directly to Prometheus. |
| 13 | +//! |
| 14 | +//! ## Setup |
| 15 | +//! |
| 16 | +//! 1. Start Prometheus with OTLP receiver: |
| 17 | +//! ```bash |
| 18 | +//! prometheus --web.enable-otlp-receiver |
| 19 | +//! ``` |
| 20 | +//! |
| 21 | +//! 2. Run this example: |
| 22 | +//! ```bash |
| 23 | +//! cd hyperactor_telemetry |
| 24 | +//! HYPERACTOR_OTEL_BACKEND=prometheus \ |
| 25 | +//! OTEL_SERVICE_NAME=prometheus-example \ |
| 26 | +//! OTEL_RESOURCE_ATTRIBUTES=environment=demo,version=1.0.0 \ |
| 27 | +//! cargo run --example prometheus_example |
| 28 | +//! ``` |
| 29 | +//! |
| 30 | +//! ## Query Examples |
| 31 | +//! |
| 32 | +//! After running, you can query Prometheus: |
| 33 | +//! - Rate of requests: `rate(http_requests_total[2m])` |
| 34 | +//! - With resource attributes: `rate(http_requests_total[2m]) * on (job, instance) group_left (environment) target_info` |
| 35 | +//! - P95 latency: `histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[2m]))` |
| 36 | +
|
| 37 | +use std::time::Duration; |
| 38 | + |
| 39 | +use hyperactor::clock::Clock; |
| 40 | +use hyperactor::clock::RealClock; |
| 41 | +use hyperactor_telemetry::declare_static_counter; |
| 42 | +use hyperactor_telemetry::declare_static_gauge; |
| 43 | +use hyperactor_telemetry::declare_static_histogram; |
| 44 | +use hyperactor_telemetry::initialize_logging_for_test; |
| 45 | +use hyperactor_telemetry::kv_pairs; |
| 46 | + |
| 47 | +// Declare some example metrics |
| 48 | +declare_static_counter!(REQUESTS_TOTAL, "http_requests_total"); |
| 49 | +declare_static_histogram!(REQUEST_DURATION, "http_request_duration_seconds"); |
| 50 | +declare_static_gauge!(ACTIVE_CONNECTIONS, "active_connections"); |
| 51 | + |
| 52 | +#[tokio::main] |
| 53 | +async fn main() -> Result<(), Box<dyn std::error::Error>> { |
| 54 | + // Configure environment if not already set |
| 55 | + // Safety: Setting environment variables at program startup before any threads are created |
| 56 | + unsafe { |
| 57 | + if std::env::var("HYPERACTOR_OTEL_BACKEND").is_err() { |
| 58 | + std::env::set_var("HYPERACTOR_OTEL_BACKEND", "prometheus"); |
| 59 | + } |
| 60 | + |
| 61 | + // Set default OpenTelemetry configuration for OTLP mode |
| 62 | + if std::env::var("OTEL_SERVICE_NAME").is_err() { |
| 63 | + std::env::set_var("OTEL_SERVICE_NAME", "prometheus-example"); |
| 64 | + } |
| 65 | + if std::env::var("OTEL_RESOURCE_ATTRIBUTES").is_err() { |
| 66 | + std::env::set_var("OTEL_RESOURCE_ATTRIBUTES", "environment=demo,version=1.0.0"); |
| 67 | + } |
| 68 | + if std::env::var("OTEL_METRIC_EXPORT_INTERVAL").is_err() { |
| 69 | + std::env::set_var("OTEL_METRIC_EXPORT_INTERVAL", "5000"); // 5 seconds for demo |
| 70 | + } |
| 71 | + } |
| 72 | + |
| 73 | + // Initialize telemetry |
| 74 | + initialize_logging_for_test(); |
| 75 | + |
| 76 | + let endpoint = std::env::var("OTEL_EXPORTER_OTLP_METRICS_ENDPOINT") |
| 77 | + .unwrap_or_else(|_| "http://localhost:9090/api/v1/otlp/v1/metrics".to_string()); |
| 78 | + |
| 79 | + println!("🚀 Starting Prometheus OTLP example..."); |
| 80 | + println!("📡 Sending metrics directly to Prometheus via OTLP"); |
| 81 | + println!("🔗 Endpoint: {}", endpoint); |
| 82 | + println!("ℹ️ Make sure Prometheus is running with --web.enable-otlp-receiver"); |
| 83 | + println!("🎯 Query Prometheus at: http://localhost:9090"); |
| 84 | + |
| 85 | + println!("✅ OTLP exporter configured - metrics will be sent automatically!"); |
| 86 | + |
| 87 | + println!("Generating some sample metrics..."); |
| 88 | + |
| 89 | + // Generate some sample metrics using hyperactor telemetry macros |
| 90 | + for i in 0..100 { |
| 91 | + // Simulate HTTP requests |
| 92 | + let status = if i % 10 == 0 { "500" } else { "200" }; |
| 93 | + let method = if i % 3 == 0 { "POST" } else { "GET" }; |
| 94 | + |
| 95 | + REQUESTS_TOTAL.add( |
| 96 | + 1, |
| 97 | + kv_pairs!( |
| 98 | + "method" => method, |
| 99 | + "status" => status, |
| 100 | + "endpoint" => "/api/data" |
| 101 | + ), |
| 102 | + ); |
| 103 | + |
| 104 | + // Simulate request durations |
| 105 | + let duration = 0.001 + (i as f64) * 0.001; // 1ms to 100ms |
| 106 | + REQUEST_DURATION.record( |
| 107 | + duration, |
| 108 | + kv_pairs!( |
| 109 | + "method" => method, |
| 110 | + "endpoint" => "/api/data" |
| 111 | + ), |
| 112 | + ); |
| 113 | + |
| 114 | + // Simulate active connections |
| 115 | + let connections = 10.0 + (i as f64 % 20.0); |
| 116 | + ACTIVE_CONNECTIONS.record( |
| 117 | + connections, |
| 118 | + kv_pairs!( |
| 119 | + "server" => "primary" |
| 120 | + ), |
| 121 | + ); |
| 122 | + |
| 123 | + // Small delay to spread metrics over time |
| 124 | + RealClock.sleep(Duration::from_millis(50)).await; |
| 125 | + |
| 126 | + if i % 20 == 0 { |
| 127 | + println!("Generated {} metrics so far...", i + 1); |
| 128 | + } |
| 129 | + } |
| 130 | + |
| 131 | + println!("✨ Finished generating metrics!"); |
| 132 | + println!("🎯 Check Prometheus for your metrics:"); |
| 133 | + println!(" - Prometheus UI: http://localhost:9090"); |
| 134 | + println!(" - Example query: rate(http_requests_total[2m])"); |
| 135 | + println!( |
| 136 | + " - Resource attributes query: rate(http_requests_total[2m]) * on (job, instance) group_left (environment) target_info" |
| 137 | + ); |
| 138 | + println!("📡 Metrics are being sent to Prometheus via OTLP every 5 seconds"); |
| 139 | + |
| 140 | + // Keep generating metrics to show real-time updates |
| 141 | + println!("🔄 Continuing to generate metrics every 10 seconds..."); |
| 142 | + for _ in 0..5 { |
| 143 | + // Generate 5 more batches then exit |
| 144 | + RealClock.sleep(Duration::from_secs(10)).await; |
| 145 | + |
| 146 | + // Generate a few more metrics |
| 147 | + REQUESTS_TOTAL.add( |
| 148 | + 1, |
| 149 | + kv_pairs!("method" => "GET", "status" => "200", "endpoint" => "/health"), |
| 150 | + ); |
| 151 | + REQUEST_DURATION.record(0.001, kv_pairs!("method" => "GET", "endpoint" => "/health")); |
| 152 | + ACTIVE_CONNECTIONS.record(15.0, kv_pairs!("server" => "primary")); |
| 153 | + |
| 154 | + println!("📊 Sent batch of metrics to Prometheus"); |
| 155 | + } |
| 156 | + |
| 157 | + println!("🎉 Example completed successfully!"); |
| 158 | + Ok(()) |
| 159 | +} |
0 commit comments