1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
//! Daemon status — observable state of the lspz daemon.
use serde::{Deserialize, Serialize};
/// Per-session statistics.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SessionInfo {
/// Pool key (e.g. "rust:rust-analyzer:/home/user/project").
pub key: String,
/// Language identifier.
pub language: String,
/// Backend command.
pub backend: String,
/// Workspace root path.
pub workspace_root: Option<String>,
/// Number of requests served by this session.
pub request_count: u64,
/// When the session was created (Unix timestamp seconds).
pub created_at: u64,
/// When the session was last used.
pub last_used_at: u64,
}
/// Full daemon status snapshot.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DaemonStatus {
/// Total client connections served.
pub total_connections: u64,
/// Total requests dispatched.
pub total_requests: u64,
/// Uptime in seconds (refreshed on status snapshot).
pub uptime_secs: u64,
/// Per-session information.
pub sessions: Vec<SessionInfo>,
/// Process start time (Unix seconds). Skipped in JSON to keep the public
/// snapshot shape stable; used only to compute [`Self::uptime_secs`].
#[serde(skip)]
started_at_unix: u64,
}
impl Default for DaemonStatus {
fn default() -> Self {
Self::new()
}
}
impl DaemonStatus {
/// Create a status object stamped with the current start time.
pub fn new() -> Self {
Self {
total_connections: 0,
total_requests: 0,
uptime_secs: 0,
sessions: Vec::new(),
started_at_unix: unix_now(),
}
}
/// Refresh [`Self::uptime_secs`] from wall clock.
pub fn refresh_uptime(&mut self) {
let now = unix_now();
self.uptime_secs = now.saturating_sub(self.started_at_unix);
}
/// Increment the lifetime connection counter.
pub fn record_connection(&mut self) {
self.total_connections = self.total_connections.saturating_add(1);
}
/// Increment the lifetime request counter.
pub fn record_request(&mut self) {
self.total_requests = self.total_requests.saturating_add(1);
}
/// Mark an existing session as just-used (looked up by pool key).
///
/// Unlike [`touch_session`](Self::touch_session), this never creates a new
/// entry — it only bumps `request_count` and `last_used_at` for an entry
/// that [`touch_session`](Self::touch_session) already created. The daemon
/// calls this when dispatching `lsp/request`, `lsp/notify`, and
/// `lsp/wait_notify` so that the idle timestamps shown to users reflect
/// real activity, not just the initial `lsp/spawn`.
pub fn touch_by_key(&mut self, key: &str) {
let now = unix_now();
if let Some(s) = self.sessions.iter_mut().find(|s| s.key == key) {
s.request_count += 1;
s.last_used_at = now;
}
}
/// Record or update a session entry.
pub fn touch_session(
&mut self,
key: &str,
language: &str,
backend: &str,
workspace_root: Option<String>,
) {
let now = unix_now();
if let Some(existing) = self.sessions.iter_mut().find(|s| s.key == key) {
existing.request_count += 1;
existing.last_used_at = now;
} else {
self.sessions.push(SessionInfo {
key: key.to_string(),
language: language.to_string(),
backend: backend.to_string(),
workspace_root,
request_count: 1,
created_at: now,
last_used_at: now,
});
}
}
}
fn unix_now() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_record_counters_and_uptime() {
let mut status = DaemonStatus::new();
assert_eq!(status.total_connections, 0);
assert_eq!(status.total_requests, 0);
status.record_connection();
status.record_request();
status.record_request();
status.refresh_uptime();
assert_eq!(status.total_connections, 1);
assert_eq!(status.total_requests, 2);
// Just started — uptime may be 0 on the same second.
assert!(status.uptime_secs < 60);
}
#[test]
fn test_status_json_omits_started_at() {
let status = DaemonStatus::new();
let json = serde_json::to_value(&status).unwrap();
assert!(json.get("started_at_unix").is_none());
assert!(json.get("total_connections").is_some());
assert!(json.get("uptime_secs").is_some());
}
}