1use crate::config::{Config, Source};
4use anyhow::{Result, ensure};
5use serde::Serialize;
6use serde_json::{Value, json};
7use std::sync::Mutex;
8use std::time::Instant;
9
10pub struct State {
11 source: Source,
12 config: Mutex<Versioned>,
13 stats: Mutex<Stats>,
14 started: Instant,
15}
16
17#[derive(Clone, Serialize)]
18pub struct Versioned {
19 pub generation: u64,
20 pub config: Config,
21}
22
23#[derive(Default, Serialize)]
24pub struct Stats {
25 pub ready: bool,
26 pub queued_requests: usize,
27 pub active_requests: usize,
28 pub completed_requests: u64,
29 pub failed_requests: u64,
30 pub rejected_requests: u64,
31 pub generated_tokens: u64,
32 pub prompt_tokens: u64,
33 pub cached_tokens: u64,
34 pub prefill_seconds: f64,
35 pub decode_seconds: f64,
36 pub current: Option<Current>,
37 pub cache: CacheStats,
38 pub memory: MemoryStats,
39 pub http_address: Option<String>,
40 pub cancelled_requests: u64,
41 pub active_state_reserved_bytes: usize,
42 pub prefill_chunk_tokens: usize,
44 pub sessions: SessionStats,
45 pub active: Vec<ActiveRequest>,
46 #[serde(skip)]
47 pub(crate) activity: crate::qwen4_exp::gpu::ExpertActivity,
48}
49
50#[derive(Default, Serialize)]
51pub struct SessionStats {
52 pub entries: usize,
53 pub bytes: usize,
54 pub evictions: u64,
55}
56
57#[derive(Serialize)]
58pub struct ActiveRequest {
59 pub id: String,
60 pub session_id: Option<String>,
61 pub phase: &'static str,
62 #[serde(flatten)]
63 pub usage: crate::server::UsageStats,
64 pub reserved_state_bytes: usize,
65}
66
67#[derive(Serialize)]
68pub struct Current {
69 pub request_id: String,
70 pub config_generation: u64,
71 pub phase: &'static str,
72 pub generated_tokens: u64,
73}
74
75#[derive(Default, Serialize)]
76pub struct CacheStats {
77 pub entries: usize,
78 pub bytes: usize,
79 pub evictions: u64,
80}
81
82#[derive(Default, Serialize)]
83pub struct MemoryStats {
84 #[serde(flatten)]
85 pub resources: crate::qwen4_exp::gpu::MemoryStats,
86 pub observed_at_uptime_seconds: f64,
87}
88
89impl State {
90 pub(super) fn summary(&self) -> Result<Value> {
91 self.activity_page(|activity| Ok(super::activity::summary(activity)))
92 }
93 pub(super) fn layers(&self, offset: usize, limit: usize) -> Result<Value> {
94 self.activity_page(|activity| super::activity::layers(activity, offset, limit))
95 }
96
97 pub(super) fn experts(&self, layer: usize, offset: usize, limit: usize) -> Result<Value> {
98 self.activity_page(|activity| super::activity::experts(activity, layer, offset, limit))
99 }
100
101 fn activity_page<T: Serialize>(
102 &self,
103 query: impl FnOnce(&crate::qwen4_exp::gpu::ExpertActivity) -> Result<T>,
104 ) -> Result<Value> {
105 let stats = self.stats.lock().unwrap();
106
107 ensure!(stats.ready, "model not ready: still loading");
108 stats.activity.validate_dimensions()?;
109
110 let snapshot = super::stats::Snapshot {
111 observation: super::stats::Observation {
112 observed_at_uptime_seconds: stats.memory.observed_at_uptime_seconds,
113 elapsed_seconds: stats.activity.elapsed_seconds,
114 gpu_timestamps_available: stats.activity.gpu_timestamps_available,
115 gpu_timing: stats.activity.gpu_timing.clone(),
116 },
117 data: query(&stats.activity)?,
118 };
119
120 drop(stats);
121
122 Ok(serde_json::to_value(snapshot)?)
123 }
124
125 pub fn new(source: Source, config: Config) -> Self {
126 Self {
127 source,
128 config: Mutex::new(Versioned {
129 generation: 1,
130 config,
131 }),
132 stats: Mutex::new(Stats::default()),
133 started: Instant::now(),
134 }
135 }
136
137 pub fn config(&self) -> Versioned {
138 self.config.lock().unwrap().clone()
139 }
140
141 pub fn show_config(&self) -> Value {
142 json!({"effective": self.config(), "source": self.source.path,
143 "reloadable_sections": ["defaults"], "restart_required_sections": ["server", "limits", "experts"]})
144 }
145
146 pub fn reload(&self) -> Result<Value> {
147 ensure!(
148 self.source.path.is_some(),
149 "server was started without --config"
150 );
151
152 let next = self.source.resolve()?;
153 let mut current = self.config.lock().unwrap();
154 let changes = current.config.restart_changes(&next);
155
156 ensure!(
157 changes.is_empty(),
158 "restart required for changed sections: {}",
159 changes.join(", ")
160 );
161
162 if current.config != next {
163 current.config = next;
164 current.generation += 1;
165 }
166
167 Ok(json!({"generation": current.generation, "config": current.config}))
168 }
169
170 pub fn update(&self, f: impl FnOnce(&mut Stats)) {
171 f(&mut self.stats.lock().unwrap());
172 }
173
174 pub fn status(&self) -> Value {
175 let config = self.config();
176
177 json!({"uptime_seconds": self.started.elapsed().as_secs_f64(),
178 "stats": *self.stats.lock().unwrap(),
179 "capabilities": {"active_sequences": config.config.limits.active_requests,
180 "sampling": true, "retained_sessions": config.config.limits.max_sessions > 0, "cancellation": true}})
181 }
182
183 pub fn begin(&self) {
184 self.update(|s| {
185 s.queued_requests -= 1;
186 s.active_requests += 1;
187 });
188 }
189
190 pub fn current(&self, request_id: &str, generation: u64, phase: &'static str, tokens: usize) {
191 self.update(|s| {
192 s.current = Some(Current {
193 request_id: request_id.to_owned(),
194 config_generation: generation,
195 phase,
196 generated_tokens: tokens as u64,
197 });
198 });
199 }
200
201 pub fn token(&self) {
202 self.update(|s| {
203 s.generated_tokens += 1;
204
205 if let Some(current) = &mut s.current {
206 current.generated_tokens += 1;
207 }
208 });
209 }
210
211 pub(crate) fn observe(
212 &self,
213 gpu: &crate::qwen4_exp::gpu::Gpu<'_>,
214 cache: &crate::prefix_cache::PrefixCache,
215 spare: &mut crate::qwen4_exp::gpu::ExpertActivity,
216 ) {
217 let (entries, bytes, evictions) = cache.stats();
218
219 gpu.copy_expert_activity(spare);
220
221 let memory = MemoryStats {
222 resources: gpu.memory_stats(),
223 observed_at_uptime_seconds: self.started.elapsed().as_secs_f64(),
224 };
225
226 self.publish_observation(
227 CacheStats {
228 entries,
229 bytes,
230 evictions,
231 },
232 memory,
233 spare,
234 );
235 }
236
237 pub(crate) fn publish_observation(
240 &self,
241 cache: CacheStats,
242 memory: MemoryStats,
243 spare: &mut crate::qwen4_exp::gpu::ExpertActivity,
244 ) {
245 self.update(|s| {
246 s.cache = cache;
247 s.memory = memory;
248
249 std::mem::swap(&mut s.activity, spare);
250 });
251 }
252}