- Replace form_urlencoded with RFC 3986 compliant URI encoding - Implement aws_uri_encode() matching AWS SigV4 spec exactly - Unreserved chars (A-Z,a-z,0-9,-,_,.,~) not encoded - All other chars percent-encoded with uppercase hex - Preserve slashes in paths, encode in query params - Normalize empty paths to '/' per AWS spec - Fix test expectations (body hash, HMAC values) - Add comprehensive SigV4 signature determinism test This fixes the canonicalization mismatch that caused signature validation failures in T047. Auth can now be enabled for production. Refs: T058.S1
157 lines
5.2 KiB
Rust
157 lines
5.2 KiB
Rust
//! Example: Query metrics from Nightlight using PromQL
|
|
//!
|
|
//! This example demonstrates how to query metrics from a running Nightlight
|
|
//! server using the Prometheus-compatible HTTP API.
|
|
//!
|
|
//! # Usage
|
|
//!
|
|
//! 1. Start a Nightlight server:
|
|
//! ```bash
|
|
//! cargo run --bin nightlight-server
|
|
//! ```
|
|
//!
|
|
//! 2. Run this example:
|
|
//! ```bash
|
|
//! cargo run --example query_metrics
|
|
//! ```
|
|
//!
|
|
//! # Prerequisites
|
|
//!
|
|
//! The server should have some data ingested via the push_metrics example first.
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
struct QueryResponse {
|
|
status: String,
|
|
data: Option<serde_json::Value>,
|
|
error: Option<String>,
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
println!("Nightlight Query Client Example");
|
|
println!("================================\n");
|
|
|
|
let client = reqwest::Client::new();
|
|
let base_url = "http://127.0.0.1:9101";
|
|
|
|
// Example 1: Instant Query
|
|
println!("1. Instant Query: up");
|
|
println!(" GET /api/v1/query?query=up");
|
|
let response = client
|
|
.get(format!("{}/api/v1/query", base_url))
|
|
.query(&[("query", "up")])
|
|
.send()
|
|
.await?;
|
|
|
|
println!(" Status: {}", response.status());
|
|
let data: QueryResponse = response.json().await?;
|
|
println!(" Response: {}\n", serde_json::to_string_pretty(&data)?);
|
|
|
|
// Example 2: Instant Query with Time
|
|
println!("2. Instant Query with Timestamp");
|
|
println!(" GET /api/v1/query?query=http_requests_total&time=1234567890000");
|
|
let response = client
|
|
.get(format!("{}/api/v1/query", base_url))
|
|
.query(&[("query", "http_requests_total"), ("time", "1234567890000")])
|
|
.send()
|
|
.await?;
|
|
|
|
println!(" Status: {}", response.status());
|
|
let data: QueryResponse = response.json().await?;
|
|
println!(" Response: {}\n", serde_json::to_string_pretty(&data)?);
|
|
|
|
// Example 3: Label Selector
|
|
println!("3. Query with Label Selector");
|
|
println!(" GET /api/v1/query?query=http_requests_total{{method=\"GET\"}}");
|
|
let response = client
|
|
.get(format!("{}/api/v1/query", base_url))
|
|
.query(&[("query", "http_requests_total{method=\"GET\"}")])
|
|
.send()
|
|
.await?;
|
|
|
|
println!(" Status: {}", response.status());
|
|
let data: QueryResponse = response.json().await?;
|
|
println!(" Response: {}\n", serde_json::to_string_pretty(&data)?);
|
|
|
|
// Example 4: Aggregation
|
|
println!("4. Aggregation Query");
|
|
println!(" GET /api/v1/query?query=sum(http_requests_total)");
|
|
let response = client
|
|
.get(format!("{}/api/v1/query", base_url))
|
|
.query(&[("query", "sum(http_requests_total)")])
|
|
.send()
|
|
.await?;
|
|
|
|
println!(" Status: {}", response.status());
|
|
let data: QueryResponse = response.json().await?;
|
|
println!(" Response: {}\n", serde_json::to_string_pretty(&data)?);
|
|
|
|
// Example 5: Range Query
|
|
let now = chrono::Utc::now().timestamp_millis();
|
|
let one_hour_ago = now - 3600 * 1000;
|
|
println!("5. Range Query");
|
|
println!(
|
|
" GET /api/v1/query_range?query=http_requests_total&start={}&end={}&step=60000",
|
|
one_hour_ago, now
|
|
);
|
|
let response = client
|
|
.get(format!("{}/api/v1/query_range", base_url))
|
|
.query(&[
|
|
("query", "http_requests_total"),
|
|
("start", &one_hour_ago.to_string()),
|
|
("end", &now.to_string()),
|
|
("step", "60000"), // 1 minute step
|
|
])
|
|
.send()
|
|
.await?;
|
|
|
|
println!(" Status: {}", response.status());
|
|
let data: QueryResponse = response.json().await?;
|
|
println!(" Response: {}\n", serde_json::to_string_pretty(&data)?);
|
|
|
|
// Example 6: Rate Function
|
|
println!("6. Rate Function");
|
|
println!(" GET /api/v1/query?query=rate(http_requests_total[5m])");
|
|
let response = client
|
|
.get(format!("{}/api/v1/query", base_url))
|
|
.query(&[("query", "rate(http_requests_total[5m])")])
|
|
.send()
|
|
.await?;
|
|
|
|
println!(" Status: {}", response.status());
|
|
let data: QueryResponse = response.json().await?;
|
|
println!(" Response: {}\n", serde_json::to_string_pretty(&data)?);
|
|
|
|
// Example 7: Label Values
|
|
println!("7. Label Values Query");
|
|
println!(" GET /api/v1/label/method/values");
|
|
let response = client
|
|
.get(format!("{}/api/v1/label/method/values", base_url))
|
|
.send()
|
|
.await?;
|
|
|
|
println!(" Status: {}", response.status());
|
|
let data: QueryResponse = response.json().await?;
|
|
println!(" Response: {}\n", serde_json::to_string_pretty(&data)?);
|
|
|
|
// Example 8: Series Metadata
|
|
println!("8. Series Metadata Query");
|
|
println!(" GET /api/v1/series");
|
|
let response = client
|
|
.get(format!("{}/api/v1/series", base_url))
|
|
.send()
|
|
.await?;
|
|
|
|
println!(" Status: {}", response.status());
|
|
let data: QueryResponse = response.json().await?;
|
|
println!(" Response: {}\n", serde_json::to_string_pretty(&data)?);
|
|
|
|
println!("\n================================");
|
|
println!("Query examples completed!");
|
|
println!("\nTip: You can use these query patterns in Grafana by configuring");
|
|
println!(" a Prometheus data source pointing to http://localhost:9101");
|
|
|
|
Ok(())
|
|
}
|