use axum::{extract::State, Json};
use serde::Serialize;
use sysinfo::{Pid, System};
use crate::error::AppError;
use crate::state::AppState;
#[derive(Debug, Clone, Serialize)]
pub struct SystemStatus {
pub cpu_percent: f32,
pub memory_percent: f32,
pub total_memory_mb: f64,
pub used_memory_mb: f64,
pub available_memory_mb: f64,
}
#[derive(Debug, Clone, Serialize)]
pub struct ProcessStatus {
pub pid: u32,
pub cpu_percent: f32,
pub memory_rss_mb: f64,
pub memory_vms_mb: f64,
pub num_threads: usize,
pub start_time: u64,
pub uptime_seconds: u64,
}
#[derive(Debug, Clone, Serialize)]
pub struct StatusResponse {
pub system: SystemStatus,
pub process: ProcessStatus,
}
#[derive(Debug, Clone, Serialize)]
pub struct ThreadInfo {
pub thread_id: u64,
pub name: String,
pub is_alive: bool,
}
pub async fn get_status(State(state): State<AppState>) -> Result<Json<StatusResponse>, AppError> {
let mut sys = System::new_all();
sys.refresh_all();
let cpu_percent = sys.global_cpu_usage();
let total_memory = sys.total_memory();
let used_memory = sys.used_memory();
let available_memory = sys.available_memory();
let system_status = SystemStatus {
cpu_percent,
memory_percent: (used_memory as f32 / total_memory as f32) * 100.0,
total_memory_mb: total_memory as f64 / 1_048_576.0,
used_memory_mb: used_memory as f64 / 1_048_576.0,
available_memory_mb: available_memory as f64 / 1_048_576.0,
};
let pid = std::process::id();
let process_status = if let Some(process) = sys.process(Pid::from_u32(pid)) {
ProcessStatus {
pid,
cpu_percent: process.cpu_usage(),
memory_rss_mb: process.memory() as f64 / 1_048_576.0,
memory_vms_mb: process.virtual_memory() as f64 / 1_048_576.0,
num_threads: 0, start_time: process.start_time(),
uptime_seconds: state.uptime_seconds(),
}
} else {
ProcessStatus {
pid,
cpu_percent: 0.0,
memory_rss_mb: 0.0,
memory_vms_mb: 0.0,
num_threads: 0,
start_time: 0,
uptime_seconds: state.uptime_seconds(),
}
};
Ok(Json(StatusResponse {
system: system_status,
process: process_status,
}))
}
pub async fn get_threads() -> Result<Json<Vec<ThreadInfo>>, AppError> {
let thread_id = format!("{:?}", std::thread::current().id());
let thread_id_num: u64 = thread_id
.trim_start_matches("ThreadId(")
.trim_end_matches(')')
.parse()
.unwrap_or(0);
let threads = vec![ThreadInfo {
thread_id: thread_id_num,
name: std::thread::current()
.name()
.unwrap_or("unknown")
.to_string(),
is_alive: true,
}];
Ok(Json(threads))
}
#[derive(Debug, Clone, Serialize)]
pub struct ProfilerStatusResponse {
pub is_profiling: bool,
pub profiler_type: Option<String>,
pub message: String,
}
pub async fn get_profiler_status() -> Result<Json<ProfilerStatusResponse>, AppError> {
Ok(Json(ProfilerStatusResponse {
is_profiling: false,
profiler_type: None,
message: "Memory profiling is not currently available in the Rust server".to_string(),
}))
}
#[derive(Debug, Clone, Serialize)]
pub struct ProfilerStartResponse {
pub status: String,
pub message: String,
}
pub async fn start_memory_profiler() -> Result<Json<ProfilerStartResponse>, AppError> {
Ok(Json(ProfilerStartResponse {
status: "info".to_string(),
message: "Memory profiling in Rust requires external tools (Valgrind, Heaptrack). \
Consider using RUST_BACKTRACE=1 and cargo-flamegraph for profiling."
.to_string(),
}))
}
pub async fn stop_memory_profiler() -> Result<Json<ProfilerStartResponse>, AppError> {
Ok(Json(ProfilerStartResponse {
status: "info".to_string(),
message: "Memory profiling is not currently active".to_string(),
}))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_system_status_serialization() {
let status = SystemStatus {
cpu_percent: 45.5,
memory_percent: 62.5,
total_memory_mb: 16384.0,
used_memory_mb: 10240.0,
available_memory_mb: 6144.0,
};
let json = serde_json::to_string(&status).unwrap();
assert!(json.contains("45.5"));
assert!(json.contains("62.5"));
}
#[test]
fn test_process_status_serialization() {
let status = ProcessStatus {
pid: 12345,
cpu_percent: 15.3,
memory_rss_mb: 256.5,
memory_vms_mb: 512.0,
num_threads: 8,
start_time: 1704067200,
uptime_seconds: 3600,
};
let json = serde_json::to_string(&status).unwrap();
assert!(json.contains("12345"));
assert!(json.contains("15.3"));
}
#[test]
fn test_thread_info_serialization() {
let info = ThreadInfo {
thread_id: 1,
name: "main".to_string(),
is_alive: true,
};
let json = serde_json::to_string(&info).unwrap();
assert!(json.contains("\"name\":\"main\""));
assert!(json.contains("\"is_alive\":true"));
}
#[test]
fn test_profiler_status_serialization() {
let status = ProfilerStatusResponse {
is_profiling: false,
profiler_type: None,
message: "Not profiling".to_string(),
};
let json = serde_json::to_string(&status).unwrap();
assert!(json.contains("\"is_profiling\":false"));
}
}