Unlock the Power of Prometheus: Collecting Metrics in Rust Web Services
Getting Started
To follow along, you’ll need a recent Rust installation (1.39+) and a way to start a local Prometheus instance – we’ll use Docker for this example. Create a new Rust project and edit the Cargo.toml
file to add the necessary dependencies:
[dependencies]
prometheus = "0.11"
warp = "0.3"
tokio = { version = "1", features = ["full"] }
rand = "0.8"
Defining Your Metrics
The first step in collecting metrics is to define what you want to measure. We’ll create a registry to record metrics throughout the program’s run, and define four key metrics:
- INCOMING_REQUESTS: count incoming requests to the /some route
- CONNECTED_CLIENTS: count clients currently connected via websockets
- RESPONSE_CODE_COLLECTOR: count different response codes of a series of randomly generated requests
- RESPONSE_TIME_COLLECTOR: collect response times of a series of randomly generated requests
Each metric has a specific data type, such as IntCounter
or HistogramVec
, which determines how the data is collected and analyzed.
use prometheus::{IntCounter, HistogramVec};
let incoming_requests = IntCounter::new("incoming_requests", "Count of incoming requests").unwrap();
let connected_clients = IntCounter::new("connected_clients", "Count of connected clients").unwrap();
let response_code_collector = HistogramVec::new("response_codes", "Response codes", vec!["200", "404", "500"]).unwrap();
let response_time_collector = HistogramVec::new("response_times", "Response times", vec!["p50", "p90", "p99"]).unwrap();
Registering Your Metrics
With your metrics defined, it’s time to register them with the registry. We’ll create a registration function that calls the register
method of the registry with boxed versions of our metrics.
use prometheus::{Registry, register};
fn register_metrics(registry: &mut Registry) {
registry.register(Box::new(incoming_requests.clone())).unwrap();
registry.register(Box::new(connected_clients.clone())).unwrap();
registry.register(Box::new(response_code_collector.clone())).unwrap();
registry.register(Box::new(response_time_collector.clone())).unwrap();
}
Tracking Metrics
Now it’s time to start collecting some metrics! We’ll implement a warp web service with three routes: one to track incoming requests, one to handle websocket connections, and one to collect random response data.
use warp::{Filter, Path, Rejection, Reply};
async fn track_incoming_request() {
incoming_requests.inc();
// Handle incoming request
}
async fn handle_websocket() {
connected_clients.inc();
// Handle websocket connection
}
async fn collect_response_data() {
// Simulate collecting response data
response_code_collector.observe(200, 1.0);
response_time_collector.observe(0.5, 1.0);
}
Publishing Metrics to Prometheus
To make our metrics available to Prometheus, we’ll implement a metrics handler that encodes the collected metrics into a buffer and returns them as a string.
use prometheus::Encoder;
async fn metrics_handler() -> Result<String, Rejection> {
let mut buffer = vec![];
let encoder = Encoder::new();
let metric_families = registry.gather();
encoder.encode(&metric_families, &mut buffer).unwrap();
Ok(String::from_utf8(buffer).unwrap())
}
Testing with Prometheus
Let’s put it all together! We’ll create a local Prometheus instance using Docker, and configure it to poll our service for new data every five seconds.
docker run -p 9090:9090 prometheus
Start the Rust web service using cargo run
, and navigate to http://localhost:9090/graph to explore the collected metrics.
Unleashing the Power of Prometheus
With our setup complete, we can start exploring the power of Prometheus. Try querying the 90th percentile of response times, or the sum of increase over time by response type. The possibilities are endless!
Query examples:
quantile(0.9, response_times)
sum(increase(response_codes[5m]))
Now that you’ve unlocked the power of Prometheus, take your application to the next level by collecting and analyzing metrics to optimize performance and identify bottlenecks.