metrics: allow customizing address in full; add tests
This commit is contained in:
parent
c20f940736
commit
be5450f9ce
3 changed files with 102 additions and 17 deletions
|
@ -16,7 +16,7 @@ self: {
|
||||||
# Generate the config.json content
|
# Generate the config.json content
|
||||||
erisConfigFile = pkgs.writeText "eris-config.json" (toJSON {
|
erisConfigFile = pkgs.writeText "eris-config.json" (toJSON {
|
||||||
listen_addr = cfg.listenAddress;
|
listen_addr = cfg.listenAddress;
|
||||||
metrics_port = cfg.metricsPort;
|
metrics_addr = cfg.metricsAddress;
|
||||||
backend_addr = cfg.backendAddress;
|
backend_addr = cfg.backendAddress;
|
||||||
min_delay = cfg.minDelay;
|
min_delay = cfg.minDelay;
|
||||||
max_delay = cfg.maxDelay;
|
max_delay = cfg.maxDelay;
|
||||||
|
@ -50,11 +50,11 @@ in {
|
||||||
example = "127.0.0.1:9999";
|
example = "127.0.0.1:9999";
|
||||||
};
|
};
|
||||||
|
|
||||||
metricsPort = mkOption {
|
metricsAddress = mkOption {
|
||||||
type = port;
|
type = port;
|
||||||
default = 9100;
|
default = "0.0.0.0:9100";
|
||||||
example = 9110;
|
example = "127.0.0.1:9100";
|
||||||
description = "The port for the Prometheus metrics endpoint.";
|
description = "The IP address and port for the Prometheus metrics endpoint.";
|
||||||
};
|
};
|
||||||
|
|
||||||
backendAddress = mkOption {
|
backendAddress = mkOption {
|
||||||
|
|
27
src/main.rs
27
src/main.rs
|
@ -43,10 +43,10 @@ struct Args {
|
||||||
|
|
||||||
#[clap(
|
#[clap(
|
||||||
long,
|
long,
|
||||||
default_value = "9100",
|
default_value = "0.0.0.0:9100",
|
||||||
help = "Port to expose Prometheus metrics and status endpoint"
|
help = "Address and port to expose Prometheus metrics and status endpoint (format: ip:port)"
|
||||||
)]
|
)]
|
||||||
metrics_port: u16,
|
metrics_addr: String,
|
||||||
|
|
||||||
#[clap(long, help = "Disable Prometheus metrics server completely")]
|
#[clap(long, help = "Disable Prometheus metrics server completely")]
|
||||||
disable_metrics: bool,
|
disable_metrics: bool,
|
||||||
|
@ -110,7 +110,7 @@ struct Args {
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
struct Config {
|
struct Config {
|
||||||
listen_addr: String,
|
listen_addr: String,
|
||||||
metrics_port: u16,
|
metrics_addr: String,
|
||||||
disable_metrics: bool,
|
disable_metrics: bool,
|
||||||
backend_addr: String,
|
backend_addr: String,
|
||||||
min_delay: u64,
|
min_delay: u64,
|
||||||
|
@ -130,7 +130,7 @@ impl Default for Config {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
listen_addr: "0.0.0.0:8888".to_string(),
|
listen_addr: "0.0.0.0:8888".to_string(),
|
||||||
metrics_port: 9100,
|
metrics_addr: "0.0.0.0:9100".to_string(),
|
||||||
disable_metrics: false,
|
disable_metrics: false,
|
||||||
backend_addr: "127.0.0.1:80".to_string(),
|
backend_addr: "127.0.0.1:80".to_string(),
|
||||||
min_delay: 1000,
|
min_delay: 1000,
|
||||||
|
@ -216,7 +216,7 @@ impl Config {
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
listen_addr: args.listen_addr.clone(),
|
listen_addr: args.listen_addr.clone(),
|
||||||
metrics_port: args.metrics_port,
|
metrics_addr: args.metrics_addr.clone(),
|
||||||
disable_metrics: args.disable_metrics,
|
disable_metrics: args.disable_metrics,
|
||||||
backend_addr: args.backend_addr.clone(),
|
backend_addr: args.backend_addr.clone(),
|
||||||
min_delay: args.min_delay,
|
min_delay: args.min_delay,
|
||||||
|
@ -1192,8 +1192,7 @@ async fn main() -> std::io::Result<()> {
|
||||||
log::info!("Metrics server disabled via configuration");
|
log::info!("Metrics server disabled via configuration");
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
let metrics_addr = format!("0.0.0.0:{}", metrics_config.metrics_port);
|
log::info!("Starting metrics server on {}", metrics_config.metrics_addr);
|
||||||
log::info!("Starting metrics server on {metrics_addr}");
|
|
||||||
|
|
||||||
let server = HttpServer::new(move || {
|
let server = HttpServer::new(move || {
|
||||||
App::new()
|
App::new()
|
||||||
|
@ -1204,12 +1203,16 @@ async fn main() -> std::io::Result<()> {
|
||||||
HttpResponse::Ok().body("Botpot Server is running. Visit /metrics for metrics or /status for status.")
|
HttpResponse::Ok().body("Botpot Server is running. Visit /metrics for metrics or /status for status.")
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
.bind(&metrics_addr);
|
.bind(&metrics_config.metrics_addr);
|
||||||
|
|
||||||
match server {
|
match server {
|
||||||
Ok(server) => Some(server.run()),
|
Ok(server) => Some(server.run()),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Failed to bind metrics server to {metrics_addr}: {e}");
|
log::error!(
|
||||||
|
"Failed to bind metrics server to {}: {}",
|
||||||
|
metrics_config.metrics_addr,
|
||||||
|
e
|
||||||
|
);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1265,7 +1268,7 @@ mod tests {
|
||||||
fn test_config_from_args() {
|
fn test_config_from_args() {
|
||||||
let args = Args {
|
let args = Args {
|
||||||
listen_addr: "127.0.0.1:8080".to_string(),
|
listen_addr: "127.0.0.1:8080".to_string(),
|
||||||
metrics_port: 9000,
|
metrics_addr: "127.0.0.1:9000".to_string(),
|
||||||
disable_metrics: true,
|
disable_metrics: true,
|
||||||
backend_addr: "127.0.0.1:8081".to_string(),
|
backend_addr: "127.0.0.1:8081".to_string(),
|
||||||
min_delay: 500,
|
min_delay: 500,
|
||||||
|
@ -1279,7 +1282,7 @@ mod tests {
|
||||||
|
|
||||||
let config = Config::from_args(&args);
|
let config = Config::from_args(&args);
|
||||||
assert_eq!(config.listen_addr, "127.0.0.1:8080");
|
assert_eq!(config.listen_addr, "127.0.0.1:8080");
|
||||||
assert_eq!(config.metrics_port, 9000);
|
assert_eq!(config.metrics_addr, "127.0.0.1:9000");
|
||||||
assert!(config.disable_metrics);
|
assert!(config.disable_metrics);
|
||||||
assert_eq!(config.backend_addr, "127.0.0.1:8081");
|
assert_eq!(config.backend_addr, "127.0.0.1:8081");
|
||||||
assert_eq!(config.min_delay, 500);
|
assert_eq!(config.min_delay, 500);
|
||||||
|
|
|
@ -59,3 +59,85 @@ pub async fn status_handler(state: web::Data<Arc<RwLock<BotState>>>) -> HttpResp
|
||||||
.content_type("application/json")
|
.content_type("application/json")
|
||||||
.body(serde_json::to_string_pretty(&info).unwrap())
|
.body(serde_json::to_string_pretty(&info).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::BotState;
|
||||||
|
use actix_web::{App, http, test};
|
||||||
|
use std::net::{IpAddr, Ipv4Addr};
|
||||||
|
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn test_metrics_handler() {
|
||||||
|
// For this test to be functional, we'll need to have some metrics
|
||||||
|
// with some values.
|
||||||
|
HITS_COUNTER.inc();
|
||||||
|
PATH_HITS.with_label_values(&["/test/path"]).inc();
|
||||||
|
UA_HITS.with_label_values(&["TestBot/1.0"]).inc();
|
||||||
|
BLOCKED_IPS.set(5.0);
|
||||||
|
ACTIVE_CONNECTIONS.set(3.0);
|
||||||
|
|
||||||
|
// Create test app
|
||||||
|
let app =
|
||||||
|
test::init_service(App::new().route("/metrics", web::get().to(metrics_handler))).await;
|
||||||
|
|
||||||
|
// Send request
|
||||||
|
let req = test::TestRequest::get().uri("/metrics").to_request();
|
||||||
|
let resp = test::call_service(&app, req).await;
|
||||||
|
|
||||||
|
// Assert response
|
||||||
|
assert_eq!(resp.status(), http::StatusCode::OK);
|
||||||
|
let body = test::read_body(resp).await;
|
||||||
|
let body_str = String::from_utf8(body.to_vec()).unwrap();
|
||||||
|
|
||||||
|
// And now, lets verify metrics content
|
||||||
|
assert!(body_str.contains("eris_hits_total"));
|
||||||
|
assert!(body_str.contains("eris_path_hits_total"));
|
||||||
|
assert!(body_str.contains("eris_ua_hits_total"));
|
||||||
|
assert!(body_str.contains("eris_blocked_ips"));
|
||||||
|
assert!(body_str.contains("eris_active_connections"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn test_status_handler() {
|
||||||
|
// Test bot state
|
||||||
|
let mut bot_state = BotState::new("/tmp/eris_test", "/tmp/eris_test_cache");
|
||||||
|
let ip1 = IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4));
|
||||||
|
let ip2 = IpAddr::V4(Ipv4Addr::new(5, 6, 7, 8));
|
||||||
|
|
||||||
|
// Test data
|
||||||
|
bot_state.blocked.insert(ip1);
|
||||||
|
bot_state.active_connections.insert(ip2);
|
||||||
|
bot_state.hits.insert(ip1, 3);
|
||||||
|
bot_state.hits.insert(ip2, 1);
|
||||||
|
|
||||||
|
let bot_state = Arc::new(RwLock::new(bot_state));
|
||||||
|
|
||||||
|
let app = test::init_service(
|
||||||
|
App::new()
|
||||||
|
.app_data(web::Data::new(bot_state.clone()))
|
||||||
|
.route("/status", web::get().to(status_handler)),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Send request
|
||||||
|
let req = test::TestRequest::get().uri("/status").to_request();
|
||||||
|
let resp = test::call_service(&app, req).await;
|
||||||
|
|
||||||
|
// Assert response
|
||||||
|
assert_eq!(resp.status(), http::StatusCode::OK);
|
||||||
|
assert_eq!(
|
||||||
|
resp.headers().get(http::header::CONTENT_TYPE).unwrap(),
|
||||||
|
"application/json"
|
||||||
|
);
|
||||||
|
|
||||||
|
let body = test::read_body(resp).await;
|
||||||
|
let status: serde_json::Value = serde_json::from_slice(&body).unwrap();
|
||||||
|
|
||||||
|
// Verify status JSON content
|
||||||
|
assert_eq!(status["status"], "running");
|
||||||
|
assert_eq!(status["blocked_ips"], 1);
|
||||||
|
assert_eq!(status["active_connections"], 1);
|
||||||
|
assert_eq!(status["hit_count"], 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue