Add some documentation

This commit is contained in:
2024-09-17 22:50:52 -04:00
parent a98f83bf2d
commit 78a94f24d2

View File

@@ -14,41 +14,51 @@ use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use tokio::time::interval; use tokio::time::interval;
/// Command line arguments structure
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)] #[command(author, version, about, long_about = None)]
struct Args { struct Args {
/// Interval in seconds between each query to the server
#[arg(short, long, default_value = "5", help="Interval in seconds between each query to the server")] #[arg(short, long, default_value = "5", help="Interval in seconds between each query to the server")]
update_interval: u64, update_interval: u64,
/// Hostname and port of the server to query
#[arg(short, long, help="Hostname and port of the server to query")] #[arg(short, long, help="Hostname and port of the server to query")]
endpoint: String, endpoint: String,
/// File containing the bearer token to use for authentication
#[arg(short, long, help="File containing the bearer token to use for authentication")] #[arg(short, long, help="File containing the bearer token to use for authentication")]
token_file: Option<String>, token_file: Option<String>,
/// Allow insecure connections (e.g., to a server with a self-signed certificate)
#[arg(short, long, help="Allow insecure connections (e.g., to a server with a self-signed certificate)")] #[arg(short, long, help="Allow insecure connections (e.g., to a server with a self-signed certificate)")]
allow_insecure: bool, allow_insecure: bool,
/// Address:Port to which the server will listen
#[arg(short, long, help="Address:Port to which the server will listen", default_value = "127.0.0.1:3030")] #[arg(short, long, help="Address:Port to which the server will listen", default_value = "127.0.0.1:3030")]
listen: String, listen: String,
} }
/// Structure for the query body sent to the server
#[derive(Serialize)] #[derive(Serialize)]
struct QueryBody { struct QueryBody {
function: String, function: String,
} }
/// Structure for the server response
#[derive(Deserialize)] #[derive(Deserialize)]
struct ServerResponse { struct ServerResponse {
data: ServerData, data: ServerData,
} }
/// Structure for the server data within the server response
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct ServerData { struct ServerData {
server_game_state: ServerGameState, server_game_state: ServerGameState,
} }
/// Structure for the server game state within the server data
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct ServerGameState { struct ServerGameState {
@@ -58,6 +68,7 @@ struct ServerGameState {
average_tick_rate: f64, average_tick_rate: f64,
} }
/// Structure for the metrics to be collected
#[derive(Clone)] #[derive(Clone)]
struct Metrics { struct Metrics {
num_connected_players: Gauge, num_connected_players: Gauge,
@@ -67,6 +78,7 @@ struct Metrics {
} }
impl Metrics { impl Metrics {
/// Creates a new instance of `Metrics`
fn new() -> Self { fn new() -> Self {
Metrics { Metrics {
num_connected_players: Gauge::new("num_connected_players", "Number of connected players").unwrap(), num_connected_players: Gauge::new("num_connected_players", "Number of connected players").unwrap(),
@@ -76,6 +88,7 @@ impl Metrics {
} }
} }
/// Updates the metrics with the provided game state
fn update(&self, game_state: &ServerGameState) { fn update(&self, game_state: &ServerGameState) {
self.num_connected_players.set(game_state.num_connected_players as f64); self.num_connected_players.set(game_state.num_connected_players as f64);
self.tech_tier.set(game_state.tech_tier as f64); self.tech_tier.set(game_state.tech_tier as f64);
@@ -84,8 +97,10 @@ impl Metrics {
} }
} }
/// Shared state type alias
type SharedState = Arc<(Metrics, Registry)>; type SharedState = Arc<(Metrics, Registry)>;
/// Handler for the `/metrics` endpoint
async fn metrics_handler(State(state): State<SharedState>) -> impl IntoResponse { async fn metrics_handler(State(state): State<SharedState>) -> impl IntoResponse {
let encoder = TextEncoder::new(); let encoder = TextEncoder::new();
let mut buffer = Vec::new(); let mut buffer = Vec::new();
@@ -93,20 +108,26 @@ async fn metrics_handler(State(state): State<SharedState>) -> impl IntoResponse
String::from_utf8(buffer).unwrap() String::from_utf8(buffer).unwrap()
} }
/// Main function
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> { async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Parse command line arguments
let args = Args::parse(); let args = Args::parse();
// Create a new registry and metrics instance
let registry = Registry::new(); let registry = Registry::new();
let metrics = Arc::new(Metrics::new()); let metrics = Arc::new(Metrics::new());
// Register metrics with the registry
registry.register(Box::new(metrics.num_connected_players.clone())).unwrap(); registry.register(Box::new(metrics.num_connected_players.clone())).unwrap();
registry.register(Box::new(metrics.tech_tier.clone())).unwrap(); registry.register(Box::new(metrics.tech_tier.clone())).unwrap();
registry.register(Box::new(metrics.total_game_duration.clone())).unwrap(); registry.register(Box::new(metrics.total_game_duration.clone())).unwrap();
registry.register(Box::new(metrics.average_tick_rate.clone())).unwrap(); registry.register(Box::new(metrics.average_tick_rate.clone())).unwrap();
// Create shared state
let shared_state: SharedState = Arc::new(((*metrics).clone(), registry)); let shared_state: SharedState = Arc::new(((*metrics).clone(), registry));
// Clone metrics for use in the update loop
let metrics_clone = Arc::clone(&metrics); let metrics_clone = Arc::clone(&metrics);
let update_interval = Duration::from_secs(args.update_interval); let update_interval = Duration::from_secs(args.update_interval);
@@ -122,6 +143,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let query_endpoint = format!("https://{}/api/v1", args.endpoint); let query_endpoint = format!("https://{}/api/v1", args.endpoint);
// Spawn a task to periodically query the server and update metrics
tokio::spawn(async move { tokio::spawn(async move {
let mut interval = interval(update_interval); let mut interval = interval(update_interval);
let query_body = QueryBody { let query_body = QueryBody {
@@ -150,10 +172,12 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
} }
}); });
// Build the application router
let app = Router::new() let app = Router::new()
.route("/metrics", get(metrics_handler)) .route("/metrics", get(metrics_handler))
.with_state(shared_state); .with_state(shared_state);
// Start the server
let addr = std::net::SocketAddr::from_str(&args.listen)?; let addr = std::net::SocketAddr::from_str(&args.listen)?;
println!("Listening on {}", addr); println!("Listening on {}", addr);
axum::Server::bind(&addr) axum::Server::bind(&addr)
@@ -161,4 +185,4 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.await?; .await?;
Ok(()) Ok(())
} }