use std::fmt::Write;
use std::sync::Arc;
use axum::extract::State;
use axum::http::header;
use axum::response::IntoResponse;
use orca_core::types::WorkloadStatus;
use crate::state::AppState;
pub async fn metrics_handler(State(state): State<Arc<AppState>>) -> impl IntoResponse {
let mut out = String::with_capacity(1024);
let services = state.services.read().await;
let _ = writeln!(out, "# HELP orca_services_total Number of services");
let _ = writeln!(out, "# TYPE orca_services_total gauge");
let _ = writeln!(out, "orca_services_total {}", services.len());
let _ = writeln!(
out,
"# HELP orca_instances_total Instance counts by service and status"
);
let _ = writeln!(out, "# TYPE orca_instances_total gauge");
for svc in services.values() {
let running = svc
.instances
.iter()
.filter(|i| i.status == WorkloadStatus::Running)
.count();
let stopped = svc.instances.len() - running;
let name = &svc.config.name;
let project = svc.config.project.as_deref().unwrap_or("");
let _ = writeln!(
out,
"orca_instances_total{{service=\"{name}\",project=\"{project}\",status=\"running\"}} {running}"
);
let _ = writeln!(
out,
"orca_instances_total{{service=\"{name}\",project=\"{project}\",status=\"stopped\"}} {stopped}"
);
}
drop(services);
let nodes = state.registered_nodes.read().await;
let _ = writeln!(out, "# HELP orca_nodes_total Number of cluster nodes");
let _ = writeln!(out, "# TYPE orca_nodes_total gauge");
let _ = writeln!(out, "orca_nodes_total {}", nodes.len());
([(header::CONTENT_TYPE, "text/plain; version=0.0.4")], out)
}
#[cfg(test)]
mod tests {
use super::*;
use axum::extract::State;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use orca_core::config::ClusterConfig;
use orca_core::testing::MockRuntime;
fn test_state() -> Arc<AppState> {
let runtime = Arc::new(MockRuntime::new());
Arc::new(AppState::new(
ClusterConfig::default(),
runtime,
None,
Arc::new(RwLock::new(HashMap::new())),
Arc::new(RwLock::new(Vec::new())),
))
}
#[tokio::test]
async fn test_metrics_format() {
let state = test_state();
let resp = metrics_handler(State(state)).await;
let response = resp.into_response();
let body = axum::body::to_bytes(response.into_body(), usize::MAX)
.await
.unwrap();
let text = String::from_utf8(body.to_vec()).unwrap();
assert!(text.contains("orca_services_total"));
assert!(text.contains("orca_nodes_total"));
assert!(text.contains("# TYPE orca_services_total gauge"));
assert!(text.contains("# TYPE orca_nodes_total gauge"));
}
}