1#[cfg(feature = "profiling_memory")]
31use crate::CoreResult;
32#[cfg(feature = "profiling_memory")]
33use std::collections::HashMap;
34#[cfg(feature = "profiling_memory")]
35use tikv_jemalloc_ctl::{epoch, stats};
36
37#[cfg(feature = "profiling_memory")]
39#[derive(Debug, Clone)]
40pub struct MemoryStats {
41 pub allocated: usize,
43 pub resident: usize,
45 pub mapped: usize,
47 pub metadata: usize,
49 pub retained: usize,
51}
52
53#[cfg(feature = "profiling_memory")]
54impl MemoryStats {
55 pub fn current() -> CoreResult<Self> {
57 epoch::mib()
59 .map_err(|e| {
60 crate::CoreError::ConfigError(crate::error::ErrorContext::new(format!(
61 "Failed to get epoch: {}",
62 e
63 )))
64 })?
65 .advance()
66 .map_err(|e| {
67 crate::CoreError::ConfigError(crate::error::ErrorContext::new(format!(
68 "Failed to advance epoch: {}",
69 e
70 )))
71 })?;
72
73 let allocated = stats::allocated::mib()
74 .map_err(|e| {
75 crate::CoreError::ConfigError(crate::error::ErrorContext::new(format!(
76 "Failed to get allocated: {}",
77 e
78 )))
79 })?
80 .read()
81 .map_err(|e| {
82 crate::CoreError::ConfigError(crate::error::ErrorContext::new(format!(
83 "Failed to read allocated: {}",
84 e
85 )))
86 })?;
87
88 let resident = stats::resident::mib()
89 .map_err(|e| {
90 crate::CoreError::ConfigError(crate::error::ErrorContext::new(format!(
91 "Failed to get resident: {}",
92 e
93 )))
94 })?
95 .read()
96 .map_err(|e| {
97 crate::CoreError::ConfigError(crate::error::ErrorContext::new(format!(
98 "Failed to read resident: {}",
99 e
100 )))
101 })?;
102
103 let mapped = stats::mapped::mib()
104 .map_err(|e| {
105 crate::CoreError::ConfigError(crate::error::ErrorContext::new(format!(
106 "Failed to get mapped: {}",
107 e
108 )))
109 })?
110 .read()
111 .map_err(|e| {
112 crate::CoreError::ConfigError(crate::error::ErrorContext::new(format!(
113 "Failed to read mapped: {}",
114 e
115 )))
116 })?;
117
118 let metadata = stats::metadata::mib()
119 .map_err(|e| {
120 crate::CoreError::ConfigError(crate::error::ErrorContext::new(format!(
121 "Failed to get metadata: {}",
122 e
123 )))
124 })?
125 .read()
126 .map_err(|e| {
127 crate::CoreError::ConfigError(crate::error::ErrorContext::new(format!(
128 "Failed to read metadata: {}",
129 e
130 )))
131 })?;
132
133 let retained = stats::retained::mib()
134 .map_err(|e| {
135 crate::CoreError::ConfigError(crate::error::ErrorContext::new(format!(
136 "Failed to get retained: {}",
137 e
138 )))
139 })?
140 .read()
141 .map_err(|e| {
142 crate::CoreError::ConfigError(crate::error::ErrorContext::new(format!(
143 "Failed to read retained: {}",
144 e
145 )))
146 })?;
147
148 Ok(Self {
149 allocated,
150 resident,
151 mapped,
152 metadata,
153 retained,
154 })
155 }
156
157 pub fn overhead_ratio(&self) -> f64 {
159 if self.allocated == 0 {
160 0.0
161 } else {
162 self.metadata as f64 / self.allocated as f64
163 }
164 }
165
166 pub fn utilization_ratio(&self) -> f64 {
168 if self.resident == 0 {
169 0.0
170 } else {
171 self.allocated as f64 / self.resident as f64
172 }
173 }
174
175 pub fn format(&self) -> String {
177 format!(
178 "Memory Stats:\n\
179 - Allocated: {} MB\n\
180 - Resident: {} MB\n\
181 - Mapped: {} MB\n\
182 - Metadata: {} MB\n\
183 - Retained: {} MB\n\
184 - Overhead: {:.2}%\n\
185 - Utilization: {:.2}%",
186 self.allocated / 1_048_576,
187 self.resident / 1_048_576,
188 self.mapped / 1_048_576,
189 self.metadata / 1_048_576,
190 self.retained / 1_048_576,
191 self.overhead_ratio() * 100.0,
192 self.utilization_ratio() * 100.0
193 )
194 }
195}
196
197#[cfg(feature = "profiling_memory")]
199pub struct MemoryProfiler {
200 baseline: Option<MemoryStats>,
201}
202
203#[cfg(feature = "profiling_memory")]
204impl MemoryProfiler {
205 pub fn new() -> Self {
207 Self { baseline: None }
208 }
209
210 pub fn set_baseline(&mut self) -> CoreResult<()> {
212 self.baseline = Some(MemoryStats::current()?);
213 Ok(())
214 }
215
216 pub fn get_stats() -> CoreResult<MemoryStats> {
218 MemoryStats::current()
219 }
220
221 pub fn get_delta(&self) -> CoreResult<Option<MemoryDelta>> {
223 if let Some(ref baseline) = self.baseline {
224 let current = MemoryStats::current()?;
225 Ok(Some(MemoryDelta {
226 allocated_delta: current.allocated as i64 - baseline.allocated as i64,
227 resident_delta: current.resident as i64 - baseline.resident as i64,
228 mapped_delta: current.mapped as i64 - baseline.mapped as i64,
229 metadata_delta: current.metadata as i64 - baseline.metadata as i64,
230 retained_delta: current.retained as i64 - baseline.retained as i64,
231 }))
232 } else {
233 Ok(None)
234 }
235 }
236
237 pub fn print_stats() -> CoreResult<()> {
239 let stats = Self::get_stats()?;
240 println!("{}", stats.format());
241 Ok(())
242 }
243}
244
245#[cfg(feature = "profiling_memory")]
246impl Default for MemoryProfiler {
247 fn default() -> Self {
248 Self::new()
249 }
250}
251
252#[cfg(feature = "profiling_memory")]
254#[derive(Debug, Clone)]
255pub struct MemoryDelta {
256 pub allocated_delta: i64,
257 pub resident_delta: i64,
258 pub mapped_delta: i64,
259 pub metadata_delta: i64,
260 pub retained_delta: i64,
261}
262
263#[cfg(feature = "profiling_memory")]
264impl MemoryDelta {
265 pub fn format(&self) -> String {
267 format!(
268 "Memory Delta:\n\
269 - Allocated: {:+} MB\n\
270 - Resident: {:+} MB\n\
271 - Mapped: {:+} MB\n\
272 - Metadata: {:+} MB\n\
273 - Retained: {:+} MB",
274 self.allocated_delta / 1_048_576,
275 self.resident_delta / 1_048_576,
276 self.mapped_delta / 1_048_576,
277 self.metadata_delta / 1_048_576,
278 self.retained_delta / 1_048_576
279 )
280 }
281}
282
283#[cfg(feature = "profiling_memory")]
285pub struct AllocationTracker {
286 snapshots: Vec<(String, MemoryStats)>,
287}
288
289#[cfg(feature = "profiling_memory")]
290impl AllocationTracker {
291 pub fn new() -> Self {
293 Self {
294 snapshots: Vec::new(),
295 }
296 }
297
298 pub fn snapshot(&mut self, label: impl Into<String>) -> CoreResult<()> {
300 let stats = MemoryStats::current()?;
301 self.snapshots.push((label.into(), stats));
302 Ok(())
303 }
304
305 pub fn snapshots(&self) -> &[(String, MemoryStats)] {
307 &self.snapshots
308 }
309
310 pub fn analyze(&self) -> AllocationAnalysis {
312 if self.snapshots.is_empty() {
313 return AllocationAnalysis {
314 total_allocated: 0,
315 peak_allocated: 0,
316 total_snapshots: 0,
317 largest_increase: None,
318 patterns: HashMap::new(),
319 };
320 }
321
322 let mut peak_allocated = 0;
323 let mut largest_increase: Option<(String, i64)> = None;
324
325 for i in 0..self.snapshots.len() {
326 let (ref label, ref stats) = self.snapshots[i];
327
328 if stats.allocated > peak_allocated {
329 peak_allocated = stats.allocated;
330 }
331
332 if i > 0 {
333 let prev_stats = &self.snapshots[i - 1].1;
334 let increase = stats.allocated as i64 - prev_stats.allocated as i64;
335
336 if let Some((_, max_increase)) = largest_increase {
337 if increase > max_increase {
338 largest_increase = Some((label.clone(), increase));
339 }
340 } else {
341 largest_increase = Some((label.clone(), increase));
342 }
343 }
344 }
345
346 let last_stats = &self
347 .snapshots
348 .last()
349 .expect("Expected at least one snapshot")
350 .1;
351
352 AllocationAnalysis {
353 total_allocated: last_stats.allocated,
354 peak_allocated,
355 total_snapshots: self.snapshots.len(),
356 largest_increase,
357 patterns: HashMap::new(),
358 }
359 }
360
361 pub fn clear(&mut self) {
363 self.snapshots.clear();
364 }
365}
366
367#[cfg(feature = "profiling_memory")]
368impl Default for AllocationTracker {
369 fn default() -> Self {
370 Self::new()
371 }
372}
373
374#[cfg(feature = "profiling_memory")]
376#[derive(Debug, Clone)]
377pub struct AllocationAnalysis {
378 pub total_allocated: usize,
379 pub peak_allocated: usize,
380 pub total_snapshots: usize,
381 pub largest_increase: Option<(String, i64)>,
382 pub patterns: HashMap<String, usize>,
383}
384
385#[cfg(feature = "profiling_memory")]
387pub fn enable_profiling() -> CoreResult<()> {
388 Ok(())
391}
392
393#[cfg(feature = "profiling_memory")]
395pub fn disable_profiling() -> CoreResult<()> {
396 Ok(())
398}
399
400#[cfg(not(feature = "profiling_memory"))]
402use crate::CoreResult;
403
404#[cfg(not(feature = "profiling_memory"))]
405#[derive(Debug, Clone)]
406pub struct MemoryStats {
407 pub allocated: usize,
408 pub resident: usize,
409 pub mapped: usize,
410 pub metadata: usize,
411 pub retained: usize,
412}
413
414#[cfg(not(feature = "profiling_memory"))]
415impl MemoryStats {
416 pub fn current() -> CoreResult<Self> {
417 Ok(Self {
418 allocated: 0,
419 resident: 0,
420 mapped: 0,
421 metadata: 0,
422 retained: 0,
423 })
424 }
425
426 pub fn format(&self) -> String {
427 "Memory profiling not enabled".to_string()
428 }
429}
430
431#[cfg(not(feature = "profiling_memory"))]
432pub struct MemoryProfiler;
433
434#[cfg(not(feature = "profiling_memory"))]
435impl MemoryProfiler {
436 pub fn new() -> Self {
437 Self
438 }
439 pub fn get_stats() -> CoreResult<MemoryStats> {
440 MemoryStats::current()
441 }
442 pub fn print_stats() -> CoreResult<()> {
443 Ok(())
444 }
445}
446
447#[cfg(not(feature = "profiling_memory"))]
448pub fn enable_profiling() -> CoreResult<()> {
449 Ok(())
450}
451
452#[cfg(test)]
453#[cfg(feature = "profiling_memory")]
454mod tests {
455 use super::*;
456
457 #[test]
458 fn test_memory_stats() {
459 let stats = MemoryStats::current();
460 assert!(stats.is_ok());
461
462 if let Ok(s) = stats {
463 println!("{}", s.format());
464 }
465 }
466
467 #[test]
468 fn test_memory_profiler() {
469 let mut profiler = MemoryProfiler::new();
470 assert!(profiler.set_baseline().is_ok());
471
472 let _vec: Vec<u8> = vec![0; 1_000_000];
474
475 let delta = profiler.get_delta();
476 assert!(delta.is_ok());
477 }
478
479 #[test]
480 fn test_allocation_tracker() {
481 let mut tracker = AllocationTracker::new();
482
483 assert!(tracker.snapshot("baseline").is_ok());
484
485 let _vec: Vec<u8> = vec![0; 1_000_000];
487
488 assert!(tracker.snapshot("after_alloc").is_ok());
489
490 let analysis = tracker.analyze();
491 assert_eq!(analysis.total_snapshots, 2);
492 }
493
494 #[test]
495 fn test_memory_delta() {
496 let delta = MemoryDelta {
497 allocated_delta: 1_048_576,
498 resident_delta: 2_097_152,
499 mapped_delta: 0,
500 metadata_delta: 0,
501 retained_delta: 0,
502 };
503
504 let formatted = delta.format();
505 assert!(formatted.contains("Allocated"));
506 }
507}