1use std::any::TypeId;
40use std::collections::HashMap;
41
42#[derive(Debug, Clone, Default)]
44pub struct ContextStats {
45 pub allocations: usize,
47 pub peak_bytes: usize,
49 pub current_bytes: usize,
51 pub typed_allocations: usize,
53 pub allocations_by_type: HashMap<TypeId, usize>,
55}
56
57impl ContextStats {
58 pub fn new() -> Self {
60 Self::default()
61 }
62
63 pub fn memory_efficiency(&self) -> f64 {
65 if self.peak_bytes > 0 {
66 self.current_bytes as f64 / self.peak_bytes as f64
67 } else {
68 1.0
69 }
70 }
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
77pub struct AllocationHandle(usize);
78
79impl AllocationHandle {
80 pub fn index(&self) -> usize {
82 self.0
83 }
84}
85
86pub struct AnalyticsContext {
98 name: String,
100 allocations: Vec<Box<[u8]>>,
102 sizes: Vec<usize>,
104 stats: ContextStats,
106}
107
108impl AnalyticsContext {
109 pub fn new(name: impl Into<String>) -> Self {
115 Self {
116 name: name.into(),
117 allocations: Vec::new(),
118 sizes: Vec::new(),
119 stats: ContextStats::new(),
120 }
121 }
122
123 pub fn with_capacity(name: impl Into<String>, expected_allocations: usize) -> Self {
128 Self {
129 name: name.into(),
130 allocations: Vec::with_capacity(expected_allocations),
131 sizes: Vec::with_capacity(expected_allocations),
132 stats: ContextStats::new(),
133 }
134 }
135
136 pub fn name(&self) -> &str {
138 &self.name
139 }
140
141 pub fn allocate(&mut self, size: usize) -> AllocationHandle {
153 let buf = vec![0u8; size].into_boxed_slice();
154 let handle = AllocationHandle(self.allocations.len());
155
156 self.allocations.push(buf);
157 self.sizes.push(size);
158
159 self.stats.allocations += 1;
160 self.stats.current_bytes += size;
161 self.stats.peak_bytes = self.stats.peak_bytes.max(self.stats.current_bytes);
162
163 handle
164 }
165
166 pub fn allocate_typed<T: Copy + Default + 'static>(
182 &mut self,
183 count: usize,
184 ) -> AllocationHandle {
185 let size = count * std::mem::size_of::<T>();
186 let handle = self.allocate(size);
187
188 self.stats.typed_allocations += 1;
190 *self
191 .stats
192 .allocations_by_type
193 .entry(TypeId::of::<T>())
194 .or_insert(0) += 1;
195
196 handle
197 }
198
199 pub fn get(&self, handle: AllocationHandle) -> &[u8] {
209 &self.allocations[handle.0]
210 }
211
212 pub fn get_mut(&mut self, handle: AllocationHandle) -> &mut [u8] {
222 &mut self.allocations[handle.0]
223 }
224
225 pub fn get_typed<T: Copy>(&self, handle: AllocationHandle) -> &[T] {
236 let bytes = &self.allocations[handle.0];
237 let len = bytes.len() / std::mem::size_of::<T>();
238 unsafe { std::slice::from_raw_parts(bytes.as_ptr() as *const T, len) }
239 }
240
241 pub fn get_typed_mut<T: Copy>(&mut self, handle: AllocationHandle) -> &mut [T] {
252 let bytes = &mut self.allocations[handle.0];
253 let len = bytes.len() / std::mem::size_of::<T>();
254 unsafe { std::slice::from_raw_parts_mut(bytes.as_mut_ptr() as *mut T, len) }
255 }
256
257 pub fn try_get(&self, handle: AllocationHandle) -> Option<&[u8]> {
261 self.allocations.get(handle.0).map(|b| b.as_ref())
262 }
263
264 pub fn try_get_mut(&mut self, handle: AllocationHandle) -> Option<&mut [u8]> {
268 self.allocations.get_mut(handle.0).map(|b| b.as_mut())
269 }
270
271 pub fn allocation_size(&self, handle: AllocationHandle) -> usize {
277 self.sizes[handle.0]
278 }
279
280 pub fn allocation_count(&self) -> usize {
282 self.allocations.len()
283 }
284
285 pub fn stats(&self) -> &ContextStats {
287 &self.stats
288 }
289
290 pub fn release_all(&mut self) {
294 self.allocations.clear();
295 self.sizes.clear();
296 self.stats.current_bytes = 0;
297 }
298
299 pub fn sub_context(&self, name: impl Into<String>) -> Self {
303 Self::new(format!("{}::{}", self.name, name.into()))
304 }
305}
306
307impl Drop for AnalyticsContext {
308 fn drop(&mut self) {
309 }
313}
314
315pub struct AnalyticsContextBuilder {
317 name: String,
318 expected_allocations: Option<usize>,
319 preallocations: Vec<usize>,
320}
321
322impl AnalyticsContextBuilder {
323 pub fn new(name: impl Into<String>) -> Self {
325 Self {
326 name: name.into(),
327 expected_allocations: None,
328 preallocations: Vec::new(),
329 }
330 }
331
332 pub fn with_expected_allocations(mut self, count: usize) -> Self {
334 self.expected_allocations = Some(count);
335 self
336 }
337
338 pub fn with_preallocation(mut self, size: usize) -> Self {
340 self.preallocations.push(size);
341 self
342 }
343
344 pub fn build(self) -> AnalyticsContext {
346 let mut ctx = match self.expected_allocations {
347 Some(cap) => {
348 AnalyticsContext::with_capacity(self.name, cap.max(self.preallocations.len()))
349 }
350 None => AnalyticsContext::new(self.name),
351 };
352
353 for size in self.preallocations {
354 ctx.allocate(size);
355 }
356
357 ctx
358 }
359}
360
361#[cfg(test)]
362mod tests {
363 use super::*;
364
365 #[test]
366 fn test_context_creation() {
367 let ctx = AnalyticsContext::new("test");
368 assert_eq!(ctx.name(), "test");
369 assert_eq!(ctx.allocation_count(), 0);
370 }
371
372 #[test]
373 fn test_context_with_capacity() {
374 let ctx = AnalyticsContext::with_capacity("test", 10);
375 assert_eq!(ctx.name(), "test");
376 assert_eq!(ctx.allocation_count(), 0);
377 }
378
379 #[test]
380 fn test_allocate() {
381 let mut ctx = AnalyticsContext::new("test");
382
383 let h1 = ctx.allocate(100);
384 let h2 = ctx.allocate(200);
385
386 assert_eq!(ctx.allocation_count(), 2);
387 assert_eq!(ctx.allocation_size(h1), 100);
388 assert_eq!(ctx.allocation_size(h2), 200);
389 assert_eq!(ctx.get(h1).len(), 100);
390 assert_eq!(ctx.get(h2).len(), 200);
391 }
392
393 #[test]
394 fn test_allocate_typed() {
395 let mut ctx = AnalyticsContext::new("test");
396
397 let h = ctx.allocate_typed::<u32>(10);
398
399 assert_eq!(ctx.allocation_size(h), 40); assert_eq!(ctx.get_typed::<u32>(h).len(), 10);
401
402 let stats = ctx.stats();
403 assert_eq!(stats.typed_allocations, 1);
404 }
405
406 #[test]
407 fn test_get_mut() {
408 let mut ctx = AnalyticsContext::new("test");
409
410 let h = ctx.allocate(10);
411 ctx.get_mut(h)[0] = 42;
412
413 assert_eq!(ctx.get(h)[0], 42);
414 }
415
416 #[test]
417 fn test_get_typed_mut() {
418 let mut ctx = AnalyticsContext::new("test");
419
420 let h = ctx.allocate_typed::<u32>(5);
421 ctx.get_typed_mut::<u32>(h)[2] = 12345;
422
423 assert_eq!(ctx.get_typed::<u32>(h)[2], 12345);
424 }
425
426 #[test]
427 fn test_try_get() {
428 let mut ctx = AnalyticsContext::new("test");
429
430 let h = ctx.allocate(10);
431 let invalid = AllocationHandle(999);
432
433 assert!(ctx.try_get(h).is_some());
434 assert!(ctx.try_get(invalid).is_none());
435 assert!(ctx.try_get_mut(h).is_some());
436 assert!(ctx.try_get_mut(invalid).is_none());
437 }
438
439 #[test]
440 fn test_stats_tracking() {
441 let mut ctx = AnalyticsContext::new("test");
442
443 ctx.allocate(100);
444 ctx.allocate(200);
445 ctx.allocate(50);
446
447 let stats = ctx.stats();
448 assert_eq!(stats.allocations, 3);
449 assert_eq!(stats.current_bytes, 350);
450 assert_eq!(stats.peak_bytes, 350);
451 }
452
453 #[test]
454 fn test_stats_peak_bytes() {
455 let mut ctx = AnalyticsContext::new("test");
456
457 ctx.allocate(100);
458 ctx.allocate(200);
459 let peak = ctx.stats().peak_bytes;
460
461 ctx.release_all();
462 assert_eq!(ctx.stats().current_bytes, 0);
463 assert_eq!(ctx.stats().peak_bytes, peak); }
465
466 #[test]
467 fn test_release_all() {
468 let mut ctx = AnalyticsContext::new("test");
469
470 let h1 = ctx.allocate(100);
471 let h2 = ctx.allocate(200);
472
473 assert_eq!(ctx.allocation_count(), 2);
474
475 ctx.release_all();
476
477 assert_eq!(ctx.allocation_count(), 0);
478 assert_eq!(ctx.stats().current_bytes, 0);
479 assert!(ctx.try_get(h1).is_none());
481 assert!(ctx.try_get(h2).is_none());
482 }
483
484 #[test]
485 fn test_sub_context() {
486 let parent = AnalyticsContext::new("parent");
487 let child = parent.sub_context("child");
488
489 assert_eq!(child.name(), "parent::child");
490 }
491
492 #[test]
493 fn test_builder() {
494 let ctx = AnalyticsContextBuilder::new("builder_test")
495 .with_expected_allocations(10)
496 .with_preallocation(100)
497 .with_preallocation(200)
498 .build();
499
500 assert_eq!(ctx.name(), "builder_test");
501 assert_eq!(ctx.allocation_count(), 2);
502 assert_eq!(ctx.stats().current_bytes, 300);
503 }
504
505 #[test]
506 fn test_context_stats_default() {
507 let stats = ContextStats::default();
508 assert_eq!(stats.allocations, 0);
509 assert_eq!(stats.peak_bytes, 0);
510 assert_eq!(stats.current_bytes, 0);
511 assert_eq!(stats.memory_efficiency(), 1.0);
512 }
513
514 #[test]
515 fn test_memory_efficiency() {
516 let mut ctx = AnalyticsContext::new("test");
517
518 ctx.allocate(100);
519 ctx.allocate(100);
520 assert_eq!(ctx.stats().memory_efficiency(), 1.0);
522
523 ctx.release_all();
524 assert_eq!(ctx.stats().memory_efficiency(), 0.0);
526 }
527
528 #[test]
529 fn test_handle_index() {
530 let mut ctx = AnalyticsContext::new("test");
531
532 let h0 = ctx.allocate(10);
533 let h1 = ctx.allocate(20);
534 let h2 = ctx.allocate(30);
535
536 assert_eq!(h0.index(), 0);
537 assert_eq!(h1.index(), 1);
538 assert_eq!(h2.index(), 2);
539 }
540
541 #[test]
542 fn test_zero_allocation() {
543 let mut ctx = AnalyticsContext::new("test");
544
545 let h = ctx.allocate(0);
546 assert_eq!(ctx.get(h).len(), 0);
547 assert_eq!(ctx.allocation_size(h), 0);
548 }
549
550 #[test]
551 fn test_large_allocation() {
552 let mut ctx = AnalyticsContext::new("test");
553
554 let h = ctx.allocate(1024 * 1024);
556 assert_eq!(ctx.get(h).len(), 1024 * 1024);
557 assert_eq!(ctx.stats().current_bytes, 1024 * 1024);
558 }
559}