Skip to main content

yggdrasil/api/database/
system_status.rs

1#![allow(clippy::unused_unit, deprecated)]
2
3//! 服务器状态聚合查询(只读):应用内指标 + 主机层指标。
4//!
5//! 应用内:DB 连接池、moka 缓存命中率、活跃会话数、进程运行时间。
6//! 主机层:sysinfo 后台采样快照(CPU/内存/磁盘/load),由 [`crate::tasks::sysinfo_sampler`] 维护。
7
8use crate::api::error::ApiError;
9use leptos::prelude::*;
10use serde::{Deserialize, Serialize};
11
12// admin 鉴权 + AppError 仅在 server 构建里被 server function 体引用。
13#[cfg(feature = "server")]
14use crate::api::auth::get_current_admin_user;
15#[cfg(feature = "server")]
16use crate::api::error::AppError;
17
18/// 单个缓存的统计快照(前端展示用)。
19#[derive(Serialize, Deserialize, Debug, Clone)]
20pub struct CacheStat {
21    pub name: String,
22    pub entry_count: u64,
23    pub hits: u64,
24    pub misses: u64,
25    /// 命中率(0.0–1.0)。
26    pub hit_rate: f64,
27}
28
29/// 服务器状态聚合数据。
30#[derive(Serialize, Deserialize, Debug, Clone)]
31pub struct ServerStatus {
32    /// 进程运行时间(秒)。
33    pub uptime_secs: u64,
34    /// DB 连接池总大小(已创建连接数)。
35    pub pool_size: usize,
36    /// 连接池最大容量。
37    pub pool_max_size: usize,
38    /// 当前空闲可用连接数。
39    pub pool_available: usize,
40    /// 正在等待获取连接的请求数。
41    pub pool_waiting: usize,
42    /// 有效会话数(sessions 表 expires_at > now())。
43    pub active_sessions: i64,
44    /// 主机层指标快照(CPU/内存/磁盘等)。
45    pub host: crate::models::system::SystemSnapshot,
46    /// 各缓存命中率与条目数。
47    pub caches: Vec<CacheStat>,
48}
49
50/// 进程启动时刻(LazyLock),用于计算运行时间。
51#[cfg(feature = "server")]
52static STARTED_AT: std::sync::LazyLock<std::time::Instant> =
53    std::sync::LazyLock::new(std::time::Instant::now);
54
55/// 获取服务器状态(只读,管理员)。
56#[server(GetServerStatus, endpoint = "get_server_status", input = leptos::server_fn::codec::Json)]
57pub async fn get_server_status() -> Result<ServerStatus, ApiError> {
58    let _user = get_current_admin_user().await?;
59
60    #[cfg(feature = "server")]
61    {
62        use crate::cache::cache_stats;
63        use crate::db::pool::{get_conn, DB_POOL};
64
65        // 连接池状态
66        let pool_status = DB_POOL.status();
67        let pool_size = pool_status.size;
68        let pool_max_size = pool_status.max_size;
69        let pool_available = pool_status.available;
70        let pool_waiting = pool_status.waiting;
71
72        // 活跃会话数
73        let client = get_conn().await.map_err(AppError::db_conn)?;
74        let active_sessions: i64 = client
75            .query_one(
76                "SELECT count(*) FROM sessions WHERE expires_at > now()",
77                &[],
78            )
79            .await
80            .map_err(AppError::query)?
81            .get(0);
82
83        // 进程运行时间
84        let uptime_secs = STARTED_AT.elapsed().as_secs();
85
86        // 主机层快照
87        let host = crate::tasks::sysinfo_sampler::read_snapshot().await;
88
89        // 缓存统计
90        let caches = cache_stats()
91            .into_iter()
92            .map(|s| CacheStat {
93                name: s.name.to_string(),
94                entry_count: s.entry_count,
95                hits: s.hits,
96                misses: s.misses,
97                hit_rate: s.hit_rate,
98            })
99            .collect();
100
101        Ok(ServerStatus {
102            uptime_secs,
103            pool_size,
104            pool_max_size,
105            pool_available,
106            pool_waiting,
107            active_sessions,
108            host,
109            caches,
110        })
111    }
112    #[cfg(not(feature = "server"))]
113    {
114        Ok(ServerStatus {
115            uptime_secs: 0,
116            pool_size: 0,
117            pool_max_size: 0,
118            pool_available: 0,
119            pool_waiting: 0,
120            active_sessions: 0,
121            host: crate::models::system::SystemSnapshot::default(),
122            caches: vec![],
123        })
124    }
125}