use axum::Json;
use axum::extract::{Path, State};
use axum::http::StatusCode;
use chrono::{DateTime, Utc};
use serde::Serialize;
use sqlx::{Row, SqlitePool};
use tracing::warn;
#[derive(Serialize)]
pub struct ProcessesResponse {
pub pc_id: String,
pub latest_at: Option<DateTime<Utc>>,
pub processes: Vec<ProcessRow>,
}
#[derive(Serialize)]
pub struct ProcessRow {
pub pid: i64,
pub name: String,
pub cpu_pct: f64,
pub rss_bytes: i64,
pub disk_read_bytes_per_sec: Option<f64>,
pub disk_written_bytes_per_sec: Option<f64>,
}
pub async fn processes(
State(pool): State<SqlitePool>,
Path(pc_id): Path<String>,
) -> Result<Json<ProcessesResponse>, StatusCode> {
let latest_at: Option<DateTime<Utc>> =
sqlx::query_scalar("SELECT MAX(at) FROM process_perf_samples WHERE pc_id = ?")
.bind(&pc_id)
.fetch_one(&pool)
.await
.map_err(|e| {
warn!(error = %e, pc_id, "process_perf latest_at query");
StatusCode::INTERNAL_SERVER_ERROR
})?;
let Some(at) = latest_at else {
return Ok(Json(ProcessesResponse {
pc_id,
latest_at: None,
processes: vec![],
}));
};
let rows = sqlx::query(
"SELECT pid, name, cpu_pct, rss_bytes,
disk_read_bytes_per_sec, disk_written_bytes_per_sec
FROM process_perf_samples
WHERE pc_id = ? AND at = ?
ORDER BY cpu_pct DESC",
)
.bind(&pc_id)
.bind(at)
.fetch_all(&pool)
.await
.map_err(|e| {
warn!(error = %e, pc_id, "process_perf rows query");
StatusCode::INTERNAL_SERVER_ERROR
})?;
let processes = rows
.into_iter()
.map(|r| ProcessRow {
pid: r.try_get("pid").unwrap_or(0),
name: r.try_get("name").unwrap_or_default(),
cpu_pct: r.try_get("cpu_pct").unwrap_or(0.0),
rss_bytes: r.try_get("rss_bytes").unwrap_or(0),
disk_read_bytes_per_sec: r.try_get("disk_read_bytes_per_sec").ok(),
disk_written_bytes_per_sec: r.try_get("disk_written_bytes_per_sec").ok(),
})
.collect();
Ok(Json(ProcessesResponse {
pc_id,
latest_at: Some(at),
processes,
}))
}