1use dashmap::DashMap;
9use parking_lot::RwLock;
10use serde::{Deserialize, Serialize};
11use std::collections::VecDeque;
12use std::sync::atomic::{AtomicUsize, Ordering};
13use std::sync::Arc;
14
15use crate::aggregated_stats::AggregatedStatsManager;
16use crate::error_stats::ErrorStatsManager;
17use crate::latency_histogram::LatencyHistogram;
18use crate::metrics::QueryMetrics;
19use crate::profile::QueryProfile;
20use crate::slow_query_logger::{SlowQueryConfig, SlowQueryLogger};
21
22use super::metric_type::MetricType;
23use super::metric_value::SpaceMetrics;
24
25#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
27pub struct TimeseriesBucket {
28 pub second: u64,
29 pub queries: u64,
30 pub total_latency_us: u64,
31 pub errors: u64,
32}
33
34impl TimeseriesBucket {
35 pub fn avg_latency_ms(&self) -> f64 {
36 if self.queries == 0 {
37 0.0
38 } else {
39 self.total_latency_us as f64 / self.queries as f64 / 1000.0
40 }
41 }
42}
43
44#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
46pub struct ResourceSample {
47 pub second: u64,
48 pub memory_used_bytes: u64,
49 pub memory_total_bytes: u64,
50 pub cpu_usage_percent: f64,
51}
52
53fn unix_secs_now() -> u64 {
54 std::time::SystemTime::now()
55 .duration_since(std::time::UNIX_EPOCH)
56 .unwrap_or_default()
57 .as_secs()
58}
59
60#[derive(Debug)]
64pub struct StatsManager {
65 pub(crate) metrics: Arc<DashMap<MetricType, Arc<super::metric_value::MetricValue>>>,
66 pub(crate) space_metrics: Arc<DashMap<String, SpaceMetrics>>,
67 pub(crate) index_metrics: Arc<DashMap<String, SpaceMetrics>>,
68 pub(crate) last_query_metrics: Arc<RwLock<Option<QueryMetrics>>>,
69 pub(crate) query_metrics_history: Arc<RwLock<VecDeque<QueryMetrics>>>,
70 pub(crate) bloom_lock: Arc<parking_lot::Mutex<()>>,
71 pub(crate) query_profiles: Arc<RwLock<VecDeque<QueryProfile>>>,
72 pub(crate) query_latency_histogram: Arc<RwLock<LatencyHistogram>>,
73 pub(crate) search_latency_histogram: Arc<RwLock<LatencyHistogram>>,
74 pub(crate) monitoring_enabled: bool,
75 pub(crate) profile_cache_size: usize,
76 pub(crate) slow_query_threshold_us: u64,
77 pub(crate) error_stats: ErrorStatsManager,
78 pub(crate) slow_query_logger: Option<Arc<SlowQueryLogger>>,
79 pub(crate) aggregated_stats: AggregatedStatsManager,
80 pub(crate) timeseries: Arc<RwLock<VecDeque<TimeseriesBucket>>>,
81 pub(crate) resource_series: Arc<RwLock<VecDeque<ResourceSample>>>,
82 pub(crate) timeseries_capacity: Arc<AtomicUsize>,
83}
84
85impl StatsManager {
86 pub fn space_key(space_id: u64) -> String {
93 format!("space_{}", space_id)
94 }
95
96 pub fn space_key_for_name(name: &str) -> String {
97 format!("space_name:{name}")
98 }
99
100 pub fn target_key(target: &str) -> String {
101 format!("target:{target}")
102 }
103
104 pub fn rebuild_key(target: &str, phase: &str) -> String {
105 format!("rebuild:{target}:{phase}")
106 }
107
108 pub fn index_key(space_id: u64, index_name: &str) -> String {
109 format!("space_{}.{}", space_id, index_name)
110 }
111
112 pub fn unknown_index_key(index_name: &str) -> String {
113 format!("unknown.{}", index_name)
114 }
115
116 pub fn new() -> Self {
117 Self {
118 metrics: Arc::new(DashMap::new()),
119 space_metrics: Arc::new(DashMap::new()),
120 index_metrics: Arc::new(DashMap::new()),
121 last_query_metrics: Arc::new(RwLock::new(None)),
122 query_metrics_history: Arc::new(RwLock::new(VecDeque::with_capacity(1000))),
123 bloom_lock: Arc::new(parking_lot::Mutex::new(())),
124 query_profiles: Arc::new(RwLock::new(VecDeque::with_capacity(1000))),
125 query_latency_histogram: Arc::new(RwLock::new(LatencyHistogram::new(10000))),
126 search_latency_histogram: Arc::new(RwLock::new(LatencyHistogram::new(10000))),
127 monitoring_enabled: true,
128 profile_cache_size: 1000,
129 slow_query_threshold_us: 1_000_000,
130 error_stats: ErrorStatsManager::new(),
131 slow_query_logger: None,
132 aggregated_stats: AggregatedStatsManager::new(),
133 timeseries: Arc::new(RwLock::new(VecDeque::with_capacity(3600))),
134 resource_series: Arc::new(RwLock::new(VecDeque::with_capacity(3600))),
135 timeseries_capacity: Arc::new(AtomicUsize::new(3600)),
136 }
137 }
138
139 pub fn with_config(
140 monitoring_enabled: bool,
141 profile_cache_size: usize,
142 slow_query_threshold_us: u64,
143 ) -> Self {
144 Self {
145 metrics: Arc::new(DashMap::new()),
146 space_metrics: Arc::new(DashMap::new()),
147 index_metrics: Arc::new(DashMap::new()),
148 last_query_metrics: Arc::new(RwLock::new(None)),
149 query_metrics_history: Arc::new(RwLock::new(VecDeque::with_capacity(1000))),
150 bloom_lock: Arc::new(parking_lot::Mutex::new(())),
151 query_profiles: Arc::new(RwLock::new(VecDeque::with_capacity(profile_cache_size))),
152 query_latency_histogram: Arc::new(RwLock::new(LatencyHistogram::new(10000))),
153 search_latency_histogram: Arc::new(RwLock::new(LatencyHistogram::new(10000))),
154 monitoring_enabled,
155 profile_cache_size,
156 slow_query_threshold_us,
157 error_stats: ErrorStatsManager::new(),
158 slow_query_logger: None,
159 aggregated_stats: AggregatedStatsManager::new(),
160 timeseries: Arc::new(RwLock::new(VecDeque::with_capacity(3600))),
161 resource_series: Arc::new(RwLock::new(VecDeque::with_capacity(3600))),
162 timeseries_capacity: Arc::new(AtomicUsize::new(3600)),
163 }
164 }
165
166 pub fn with_slow_query_logger(
168 monitoring_enabled: bool,
169 profile_cache_size: usize,
170 slow_query_threshold_us: u64,
171 slow_query_config: SlowQueryConfig,
172 ) -> Result<Self, std::io::Error> {
173 let logger = Arc::new(SlowQueryLogger::new(slow_query_config)?);
174
175 Ok(Self {
176 metrics: Arc::new(DashMap::new()),
177 space_metrics: Arc::new(DashMap::new()),
178 index_metrics: Arc::new(DashMap::new()),
179 last_query_metrics: Arc::new(RwLock::new(None)),
180 query_metrics_history: Arc::new(RwLock::new(VecDeque::with_capacity(1000))),
181 bloom_lock: Arc::new(parking_lot::Mutex::new(())),
182 query_profiles: Arc::new(RwLock::new(VecDeque::with_capacity(profile_cache_size))),
183 query_latency_histogram: Arc::new(RwLock::new(LatencyHistogram::new(10000))),
184 search_latency_histogram: Arc::new(RwLock::new(LatencyHistogram::new(10000))),
185 monitoring_enabled,
186 profile_cache_size,
187 slow_query_threshold_us,
188 error_stats: ErrorStatsManager::new(),
189 slow_query_logger: Some(logger),
190 aggregated_stats: AggregatedStatsManager::new(),
191 timeseries: Arc::new(RwLock::new(VecDeque::with_capacity(3600))),
192 resource_series: Arc::new(RwLock::new(VecDeque::with_capacity(3600))),
193 timeseries_capacity: Arc::new(AtomicUsize::new(3600)),
194 })
195 }
196
197 pub fn slow_query_threshold_us(&self) -> u64 {
198 self.slow_query_threshold_us
199 }
200
201 pub fn timeseries_capacity(&self) -> usize {
202 self.timeseries_capacity.load(Ordering::Relaxed)
203 }
204
205 pub fn set_timeseries_capacity(&self, capacity: usize) {
206 let capacity = capacity.clamp(60, 86400);
207 self.timeseries_capacity.store(capacity, Ordering::Relaxed);
208 let mut series = self.timeseries.write();
209 while series.len() > capacity {
210 series.pop_front();
211 }
212 let mut resources = self.resource_series.write();
213 while resources.len() > capacity {
214 resources.pop_front();
215 }
216 }
217
218 pub fn set_histogram_max_samples(&self, max_samples: usize) {
219 let max_samples = max_samples.clamp(100, 100_000);
220 self.query_latency_histogram
221 .write()
222 .set_max_samples(max_samples);
223 self.search_latency_histogram
224 .write()
225 .set_max_samples(max_samples);
226 }
227
228 pub(crate) fn record_timeseries_query(&self, latency_us: u64, is_error: bool) {
229 let second = unix_secs_now();
230 let capacity = self.timeseries_capacity();
231 let mut series = self.timeseries.write();
232 if let Some(back) = series.back_mut() {
233 if back.second == second {
234 back.queries += 1;
235 back.total_latency_us += latency_us;
236 if is_error {
237 back.errors += 1;
238 }
239 return;
240 }
241 }
242 series.push_back(TimeseriesBucket {
243 second,
244 queries: 1,
245 total_latency_us: latency_us,
246 errors: if is_error { 1 } else { 0 },
247 });
248 while series.len() > capacity {
249 series.pop_front();
250 }
251 }
252
253 pub fn record_resource_sample(
254 &self,
255 memory_used_bytes: u64,
256 memory_total_bytes: u64,
257 cpu_usage_percent: f64,
258 ) {
259 let second = unix_secs_now();
260 let capacity = self.timeseries_capacity();
261 let mut series = self.resource_series.write();
262 if let Some(back) = series.back_mut() {
263 if back.second == second {
264 back.memory_used_bytes = memory_used_bytes;
265 back.memory_total_bytes = memory_total_bytes;
266 back.cpu_usage_percent = cpu_usage_percent;
267 return;
268 }
269 }
270 series.push_back(ResourceSample {
271 second,
272 memory_used_bytes,
273 memory_total_bytes,
274 cpu_usage_percent,
275 });
276 while series.len() > capacity {
277 series.pop_front();
278 }
279 }
280
281 pub fn query_timeseries(&self, window_secs: u64) -> Vec<TimeseriesBucket> {
282 let cutoff = unix_secs_now().saturating_sub(window_secs);
283 self.timeseries
284 .read()
285 .iter()
286 .filter(|b| b.second >= cutoff)
287 .cloned()
288 .collect()
289 }
290
291 pub fn resource_timeseries(&self, window_secs: u64) -> Vec<ResourceSample> {
292 let cutoff = unix_secs_now().saturating_sub(window_secs);
293 self.resource_series
294 .read()
295 .iter()
296 .filter(|s| s.second >= cutoff)
297 .cloned()
298 .collect()
299 }
300}
301
302impl Default for StatsManager {
303 fn default() -> Self {
304 Self::new()
305 }
306}