1use std::collections::HashMap;
7use std::future::Future;
8use std::sync::atomic::{AtomicU64, Ordering};
9use std::sync::{Arc, Mutex};
10use std::thread::ThreadId;
11
12use super::async_types::{
13 AsyncAllocation, AsyncError, AsyncResult, AsyncSnapshot, AsyncStats, TrackedFuture,
14};
15use super::task_profile::{TaskMemoryProfile, TaskType};
16
17pub fn generate_unique_task_id() -> u64 {
20 crate::task_registry::generate_task_id()
21}
22
23static THREAD_COUNTER: AtomicU64 = AtomicU64::new(1);
25
26thread_local! {
27 static THREAD_ID: u64 = THREAD_COUNTER.fetch_add(1, Ordering::Relaxed);
28}
29
30pub fn current_thread_id() -> u64 {
33 THREAD_ID.with(|id| *id)
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
39pub struct TrackerContext {
40 pub thread_id: u64,
41 pub task_id: Option<u64>,
42 pub tokio_task_id: Option<u64>,
43}
44
45impl TrackerContext {
46 pub fn capture() -> Self {
49 let _suppression = crate::core::allocator::TrackingGuard::acquire();
50 let task_id_from_context = TASK_CONTEXT.try_with(|ctx| *ctx).ok().flatten();
51 let tokio_task_id = tokio::task::try_id().and_then(|id| id.to_string().parse().ok());
52
53 Self {
54 thread_id: current_thread_id(),
55 task_id: task_id_from_context.or_else(AsyncTracker::get_current_task),
56 tokio_task_id,
57 }
58 }
59}
60
61#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
63pub struct TaskReport {
64 pub task_name: String,
65 pub task_type: TaskType,
66 pub efficiency_score: f64,
67 pub cpu_efficiency: f64,
68 pub memory_efficiency: f64,
69 pub io_efficiency: f64,
70 pub bottleneck: String,
71 pub recommendations: Vec<String>,
72}
73
74#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
76pub struct ResourceRanking {
77 pub task_name: String,
78 pub task_type: TaskType,
79 pub cpu_usage: f64,
80 pub memory_usage_mb: f64,
81 pub io_usage_mb: f64,
82 pub network_usage_mb: f64,
83 pub gpu_usage: f64,
84 pub overall_score: f64,
85}
86
87static GLOBAL_TRACKER: Mutex<Option<Arc<AsyncTracker>>> = Mutex::new(None);
89
90mod task_context;
91pub use task_context::TaskGuard;
92
93tokio::task_local! {
94 static TASK_CONTEXT: Option<u64>;
95}
96
97mod dealloc;
98mod future;
99mod queries;
100use future::{ContextualFuture, TaskLifecycleGuard};
101pub use queries::get_memory_snapshot;
102
103fn numeric_thread_id(thread_id: ThreadId) -> Option<u64> {
108 if thread_id == std::thread::current().id() {
109 Some(crate::utils::current_thread_id_u64())
110 } else {
111 None
112 }
113}
114
115pub struct AsyncTracker {
117 allocations: Arc<Mutex<HashMap<usize, Vec<AsyncAllocation>>>>,
119 generations: Arc<Mutex<HashMap<usize, u64>>>,
122 stats: Arc<Mutex<AsyncStats>>,
123 profiles: Arc<Mutex<HashMap<u64, TaskMemoryProfile>>>,
124 initialized: Arc<Mutex<bool>>,
125 event_store: Option<crate::event_store::SharedEventStore>,
126}
127
128impl AsyncTracker {
129 pub(crate) fn lifecycle_profiles(&self) -> Vec<TaskMemoryProfile> {
130 let _guard = crate::core::allocator::TrackingGuard::acquire();
131 self.profiles
132 .lock()
133 .map(|p| p.values().cloned().collect())
134 .unwrap_or_default()
135 }
136
137 pub fn new() -> Self {
138 Self {
139 allocations: Arc::new(Mutex::new(HashMap::new())),
140 generations: Arc::new(Mutex::new(HashMap::new())),
141 stats: Arc::new(Mutex::new(AsyncStats::default())),
142 profiles: Arc::new(Mutex::new(HashMap::new())),
143 initialized: Arc::new(Mutex::new(false)),
144 event_store: None,
145 }
146 }
147
148 pub fn set_current_task(task_id: u64) {
149 task_context::set(task_id);
150 }
151
152 pub fn clear_current_task() {
153 task_context::clear();
154 }
155
156 pub fn get_current_task() -> Option<u64> {
161 task_context::current()
162 }
163
164 pub fn enter_task(task_id: u64) -> TaskGuard {
167 task_context::enter(task_id)
168 }
169
170 pub fn with_task<F, T>(task_id: u64, f: F) -> T
174 where
175 F: FnOnce() -> T,
176 {
177 let _guard = Self::enter_task(task_id);
178 f()
179 }
180
181 pub fn track_task_start(
182 &self,
183 task_id: u64,
184 name: String,
185 thread_id: ThreadId,
186 ) -> Result<(), AsyncError> {
187 self.track_task_start_internal(task_id, None, name, thread_id)?;
188 Self::set_current_task(task_id);
203 Ok(())
204 }
205
206 pub fn track_task_start_with_tokio(
207 &self,
208 task_id: u64,
209 tokio_task_id: u64,
210 name: String,
211 thread_id: ThreadId,
212 ) -> Result<(), AsyncError> {
213 self.track_task_start_internal(task_id, Some(tokio_task_id), name, thread_id)
217 }
218
219 fn track_task_start_internal(
220 &self,
221 task_id: u64,
222 tokio_task_id: Option<u64>,
223 name: String,
224 thread_id: ThreadId,
225 ) -> Result<(), AsyncError> {
226 {
227 let mut profiles = self
228 .profiles
229 .lock()
230 .map_err(|e| AsyncError::mutex_lock_failed("profiles", &e.to_string()))?;
231
232 if profiles.contains_key(&task_id) {
233 return Err(AsyncError::duplicate_task(task_id));
234 }
235
236 let mut profile = match tokio_task_id {
237 Some(id) => {
238 TaskMemoryProfile::with_tokio_id(task_id, id, name, TaskType::default())
239 }
240 None => TaskMemoryProfile::new(task_id, name, TaskType::default()),
241 };
242 profile.set_thread_id_u64(numeric_thread_id(thread_id));
246 profiles.insert(task_id, profile);
247 }
248
249 let mut stats = self
250 .stats
251 .lock()
252 .map_err(|e| AsyncError::mutex_lock_failed("stats", &e.to_string()))?;
253 stats.total_tasks += 1;
254 stats.active_tasks += 1;
255
256 Ok(())
261 }
262
263 pub fn track_task_end(&self, task_id: u64) -> Result<(), AsyncError> {
265 {
266 let mut profiles = self
267 .profiles
268 .lock()
269 .map_err(|e| AsyncError::mutex_lock_failed("profiles", &e.to_string()))?;
270
271 let profile = profiles
272 .get_mut(&task_id)
273 .ok_or_else(|| AsyncError::task_not_found(task_id))?;
274
275 if profile.is_completed() {
276 return Ok(());
277 }
278
279 profile.mark_completed();
280 }
281
282 let mut stats = self
283 .stats
284 .lock()
285 .map_err(|e| AsyncError::mutex_lock_failed("stats", &e.to_string()))?;
286 stats.active_tasks = stats.active_tasks.saturating_sub(1);
287
288 task_context::end(task_id);
290
291 Ok(())
292 }
293
294 pub async fn track_in_tokio_task<F, T>(&self, name: String, future: F) -> (u64, T)
309 where
310 F: Future<Output = T>,
311 {
312 let unique_task_id = generate_unique_task_id();
313 let tokio_task_id = tokio::task::try_id().and_then(|id| id.to_string().parse().ok());
314 let thread_id = std::thread::current().id();
315 let parent_id = crate::task_registry::TaskIdRegistry::current_task_id();
317
318 if let Some(tokio_id) = tokio_task_id {
319 if let Err(e) =
320 self.track_task_start_with_tokio(unique_task_id, tokio_id, name.clone(), thread_id)
321 {
322 tracing::warn!("Failed to track task start: {e}");
323 }
324 } else if let Err(e) =
325 self.track_task_start_internal(unique_task_id, None, name.clone(), thread_id)
326 {
327 tracing::warn!("Failed to track task start: {e}");
328 }
329
330 crate::task_registry::global_registry().register_explicit_task_with_parent(
331 unique_task_id,
332 &name,
333 parent_id,
334 );
335 let lifecycle = future::BorrowedLifecycleGuard::new(self, unique_task_id);
336
337 let contextual = ContextualFuture::new(future, unique_task_id);
340 let output = contextual.await;
341
342 drop(lifecycle);
343
344 (unique_task_id, output)
345 }
346
347 pub fn detect_zombie_tasks(&self) -> Vec<u64> {
356 let _suppression = crate::core::allocator::TrackingGuard::acquire();
357 self.get_all_profiles()
358 .iter()
359 .filter(|p| !p.is_completed())
360 .map(|p| p.task_id)
361 .collect()
362 }
363
364 pub fn zombie_task_stats(&self) -> (usize, usize) {
368 let profiles = self.get_all_profiles();
369 let zombies = profiles.iter().filter(|p| !p.is_completed()).count();
370 let total = profiles.len();
371 (zombies, total)
372 }
373
374 pub fn track_allocation_auto(
375 &self,
376 ptr: usize,
377 size: usize,
378 var_name: Option<String>,
379 type_name: Option<String>,
380 ) {
381 if let Some(task_id) = Self::get_current_task() {
382 self.track_allocation_with_location(ptr, size, task_id, var_name, type_name, None);
383 }
384 }
385
386 pub fn track_allocation(&self, ptr: usize, size: usize, task_id: u64) {
388 self.track_allocation_with_location(ptr, size, task_id, None, None, None);
389 }
390
391 pub fn track_allocation_with_location(
399 &self,
400 ptr: usize,
401 size: usize,
402 task_id: u64,
403 var_name: Option<String>,
404 type_name: Option<String>,
405 source_location: Option<super::async_types::SourceLocation>,
406 ) {
407 let mut previous: Option<(u64, u64)> = None;
408 let mut generation = 1u64;
409
410 {
411 let Ok(mut allocations) = self.allocations.lock() else {
412 tracing::error!("Failed to acquire allocations lock during track_allocation");
413 return;
414 };
415 let entries = allocations.entry(ptr).or_default();
416
417 if let Some(existing) = entries.last_mut() {
418 if existing.size == size {
419 existing.var_name = var_name;
421 existing.type_name = type_name;
422 if source_location.is_some() {
423 existing.source_location = source_location;
424 }
425 return;
426 }
427 previous = Some((existing.task_id, existing.size as u64));
432 entries.pop();
433 }
434
435 if let Ok(mut generations) = self.generations.lock() {
438 let counter = generations.entry(ptr).or_insert(0);
439 *counter = counter.saturating_add(1);
440 generation = *counter;
441 }
442
443 entries.push(AsyncAllocation {
444 ptr,
445 size,
446 timestamp: Self::now(),
447 task_id,
448 var_name,
449 type_name,
450 source_location,
451 generation,
452 });
453 }
454
455 let size_u64 = size as u64;
456
457 if let Some((previous_task, previous_size)) = previous {
458 if let Ok(mut profiles) = self.profiles.lock() {
463 if let Some(profile) = profiles.get_mut(&previous_task) {
464 profile.record_deallocation(previous_size);
465 }
466 if let Some(profile) = profiles.get_mut(&task_id) {
467 profile.record_allocation(size_u64);
468 profile.set_thread_id_u64(Some(crate::utils::current_thread_id_u64()));
469 }
470 } else {
471 tracing::error!("Failed to acquire profiles lock during track_allocation");
472 }
473
474 if let Ok(mut stats) = self.stats.lock() {
475 stats.total_allocations += 1;
476 stats.total_memory += size;
477 stats.total_deallocated += previous_size;
478 stats.active_memory = stats
479 .active_memory
480 .saturating_sub(previous_size as usize)
481 .saturating_add(size);
482 if stats.active_memory > stats.peak_memory {
483 stats.peak_memory = stats.active_memory;
484 }
485 } else {
486 tracing::error!("Failed to acquire stats lock during track_allocation");
487 }
488
489 return;
490 }
491
492 if let Ok(mut profiles) = self.profiles.lock() {
493 if let Some(profile) = profiles.get_mut(&task_id) {
494 profile.record_allocation(size_u64);
495 profile.set_thread_id_u64(Some(crate::utils::current_thread_id_u64()));
498 }
499 } else {
500 tracing::error!("Failed to acquire profiles lock during track_allocation");
501 }
502
503 if let Ok(mut stats) = self.stats.lock() {
504 stats.total_allocations += 1;
505 stats.total_memory += size;
506 stats.active_memory += size;
507 if stats.active_memory > stats.peak_memory {
508 stats.peak_memory = stats.active_memory;
509 }
510 } else {
511 tracing::error!("Failed to acquire stats lock during track_allocation");
512 }
513 }
514
515 pub fn get_stats(&self) -> AsyncStats {
516 let _guard = crate::core::allocator::TrackingGuard::acquire();
517 if let Some((_, rebuilt)) = self.rebuilt() {
518 return queries::stats(&rebuilt);
519 }
520 if let Ok(stats) = self.stats.lock() {
521 stats.clone()
522 } else {
523 tracing::error!("Failed to acquire stats lock in get_stats");
524 AsyncStats::default()
525 }
526 }
527
528 pub fn snapshot(&self) -> AsyncSnapshot {
530 let _guard = crate::core::allocator::TrackingGuard::acquire();
532 if let Some((captured_at, rebuilt)) = self.rebuilt() {
533 return queries::snapshot(captured_at, rebuilt);
534 }
535 queries::local_snapshot(self)
536 }
537
538 pub fn get_task_profile(&self, task_id: u64) -> Option<TaskMemoryProfile> {
542 let _guard = crate::core::allocator::TrackingGuard::acquire();
543
544 if let Some((_, rebuilt)) = self.rebuilt() {
545 return rebuilt.profiles.into_iter().find(|p| p.task_id == task_id);
546 }
547 if let Ok(profiles) = self.profiles.lock() {
548 profiles.get(&task_id).cloned().map(|mut profile| {
549 profile.refresh_at(Self::now());
550 profile
551 })
552 } else {
553 tracing::error!("Failed to acquire profiles lock in get_task_profile");
554 None
555 }
556 }
557
558 pub fn get_all_profiles(&self) -> Vec<TaskMemoryProfile> {
562 let _guard = crate::core::allocator::TrackingGuard::acquire();
563
564 if let Some((_, rebuilt)) = self.rebuilt() {
565 return rebuilt.profiles;
566 }
567 if let Ok(profiles) = self.profiles.lock() {
568 let captured_at = Self::now();
569 profiles
570 .values()
571 .cloned()
572 .map(|mut profile| {
573 profile.refresh_at(captured_at);
574 profile
575 })
576 .collect()
577 } else {
578 tracing::error!("Failed to acquire profiles lock in get_all_profiles");
579 Vec::new()
580 }
581 }
582
583 pub fn is_initialized(&self) -> bool {
585 if let Ok(initialized) = self.initialized.lock() {
586 *initialized
587 } else {
588 tracing::error!("Failed to acquire initialized lock in is_initialized");
589 false
590 }
591 }
592
593 pub fn set_initialized(&self) {
595 if let Ok(mut initialized) = self.initialized.lock() {
596 *initialized = true;
597 } else {
598 tracing::error!("Failed to acquire initialized lock in set_initialized");
599 }
600 }
601
602 pub fn analyze_task(&self, task_id: u64, task_type: TaskType) -> Option<TaskReport> {
604 let _suppression = crate::core::allocator::TrackingGuard::acquire();
605 let profile = self.get_task_profile(task_id)?;
606
607 let total_bytes = profile.total_bytes as f64;
608 let total_allocations = profile.total_allocations as f64;
609 let peak_memory = profile.peak_memory as f64;
610 let duration_ms = profile.duration_ns as f64 / 1_000_000.0;
611
612 let compute_efficiency = if duration_ms > 0.0 {
613 (total_allocations / duration_ms * 1000.0).min(1.0)
614 } else {
615 0.0
616 };
617
618 let cpu_efficiency = match task_type {
619 TaskType::CpuIntensive | TaskType::IoIntensive | TaskType::GpuCompute => {
620 compute_efficiency
621 }
622 TaskType::MemoryIntensive => {
623 if total_bytes > 0.0 {
624 (peak_memory / total_bytes).min(1.0)
625 } else {
626 0.0
627 }
628 }
629 TaskType::NetworkIntensive => {
630 if total_bytes > 0.0 {
631 (total_allocations / total_bytes * 1000.0).min(1.0)
632 } else {
633 0.0
634 }
635 }
636 _ => compute_efficiency,
637 };
638
639 let memory_efficiency = if total_bytes > 0.0 {
640 (total_allocations / total_bytes * 1000.0).min(1.0)
641 } else {
642 0.0
643 };
644
645 let io_efficiency = if duration_ms > 0.0 {
646 (total_bytes / duration_ms / 1_048_576.0).min(1.0)
647 } else {
648 0.0
649 };
650
651 let efficiency_score = (cpu_efficiency + memory_efficiency + io_efficiency) / 3.0;
652
653 let bottleneck = if duration_ms > 5000.0 {
654 "Execution Time".to_string()
655 } else if peak_memory > 100.0 * 1024.0 * 1024.0 {
656 "Memory".to_string()
657 } else if total_allocations > 10000.0 {
658 "Allocations".to_string()
659 } else {
660 "None".to_string()
661 };
662
663 let mut recommendations = Vec::new();
664 if duration_ms > 5000.0 {
665 recommendations.push("Consider optimizing task execution time".to_string());
666 }
667 if peak_memory > 100.0 * 1024.0 * 1024.0 {
668 recommendations.push("Reduce peak memory usage".to_string());
669 }
670 if total_allocations > 10000.0 {
671 recommendations.push("Reduce number of allocations".to_string());
672 }
673 if recommendations.is_empty() {
674 recommendations.push("Performance is good".to_string());
675 }
676
677 Some(TaskReport {
678 task_name: profile.task_name.clone(),
679 task_type,
680 efficiency_score,
681 cpu_efficiency,
682 memory_efficiency,
683 io_efficiency,
684 bottleneck,
685 recommendations,
686 })
687 }
688
689 pub fn get_resource_rankings(&self) -> Vec<ResourceRanking> {
691 let _suppression = crate::core::allocator::TrackingGuard::acquire();
692 let profiles = self.get_all_profiles();
693
694 let mut rankings: Vec<ResourceRanking> = profiles
695 .into_iter()
696 .map(|profile| {
697 let memory_mb = profile.total_bytes as f64 / 1_048_576.0;
698 let peak_memory_mb = profile.peak_memory as f64 / 1_048_576.0;
699 let duration_ms = profile.duration_ns as f64 / 1_000_000.0;
700 let allocation_rate = profile.allocation_rate;
701
702 let overall_score = memory_mb * 0.3
703 + peak_memory_mb * 0.2
704 + allocation_rate * 0.0001
705 + duration_ms * 0.0001;
706
707 ResourceRanking {
708 task_name: profile.task_name.clone(),
709 task_type: profile.task_type,
710 cpu_usage: allocation_rate,
711 memory_usage_mb: memory_mb,
712 io_usage_mb: 0.0,
713 network_usage_mb: 0.0,
714 gpu_usage: 0.0,
715 overall_score,
716 }
717 })
718 .collect();
719
720 rankings.sort_by(|a, b| {
721 b.overall_score
722 .partial_cmp(&a.overall_score)
723 .unwrap_or(std::cmp::Ordering::Equal)
724 });
725
726 rankings
727 }
728
729 fn now() -> u64 {
731 std::time::SystemTime::now()
732 .duration_since(std::time::UNIX_EPOCH)
733 .unwrap_or_default()
734 .as_nanos() as u64
735 }
736}
737
738impl Default for AsyncTracker {
739 fn default() -> Self {
740 Self::new()
741 }
742}
743
744pub fn initialize() -> AsyncResult<()> {
746 let mut global = GLOBAL_TRACKER.lock().map_err(|_| AsyncError::System {
747 operation: Arc::from("initialize"),
748 message: Arc::from("Failed to acquire global tracker lock"),
749 })?;
750
751 if global.is_none() {
752 let tracker = AsyncTracker::new();
753 tracker.set_initialized();
754 *global = Some(Arc::new(tracker));
755 tracing::info!("Async memory tracking system initialized");
756 Ok(())
757 } else {
758 Err(AsyncError::initialization(
759 "tracker",
760 "Already initialized",
761 true,
762 ))
763 }
764}
765
766pub fn shutdown() -> AsyncResult<()> {
768 let mut global = GLOBAL_TRACKER.lock().map_err(|_| AsyncError::System {
769 operation: Arc::from("shutdown"),
770 message: Arc::from("Failed to acquire global tracker lock"),
771 })?;
772
773 if global.is_some() {
774 *global = None;
775 tracing::info!("Async memory tracking system shutdown");
776 Ok(())
777 } else {
778 Err(AsyncError::initialization(
779 "tracker",
780 "Not initialized",
781 true,
782 ))
783 }
784}
785
786#[cfg(test)]
788pub fn reset_global_tracker() {
789 if let Ok(mut global) = GLOBAL_TRACKER.lock() {
790 *global = None;
791 } else {
792 tracing::error!("Failed to acquire global tracker lock in reset_global_tracker");
793 }
794}
795
796pub fn register_global(tracker: Arc<AsyncTracker>) -> AsyncResult<()> {
801 let mut global = GLOBAL_TRACKER.lock().map_err(|_| AsyncError::System {
802 operation: Arc::from("register_global"),
803 message: Arc::from("Failed to acquire global tracker lock"),
804 })?;
805 *global = Some(tracker);
806 Ok(())
807}
808
809fn get_global_tracker() -> AsyncResult<Arc<AsyncTracker>> {
811 GLOBAL_TRACKER
812 .lock()
813 .map_err(|_| AsyncError::System {
814 operation: Arc::from("get_global_tracker"),
815 message: Arc::from("Failed to acquire global tracker lock"),
816 })?
817 .clone()
818 .ok_or_else(|| {
819 AsyncError::initialization("tracker", "Tracking system not initialized", true)
820 })
821}
822
823pub fn notify_deallocation(ptr: usize) {
831 let Ok(tracker) = get_global_tracker() else {
832 return;
833 };
834 tracker.track_deallocation(ptr);
835}
836
837pub fn create_tracked<F>(future: F) -> TrackedFuture<F>
839where
840 F: Future,
841{
842 TrackedFuture::new(future)
843}
844
845pub fn spawn_tracked<F>(future: F) -> tokio::task::JoinHandle<F::Output>
861where
862 F: Future + Send + 'static,
863 F::Output: Send + 'static,
864{
865 let task_id = generate_unique_task_id();
866
867 let parent_id = crate::task_registry::TaskIdRegistry::current_task_id();
872
873 tokio::spawn(async move {
874 let tracker = get_global_tracker().ok();
877 let task_name = format!("spawned_task_{}", task_id);
878 if let Some(ref tracker) = tracker {
879 let thread_id = std::thread::current().id();
880 let tokio_id = tokio::task::try_id().and_then(|id| id.to_string().parse().ok());
885 if let Some(tokio_id) = tokio_id {
886 let _ = tracker.track_task_start_with_tokio(
887 task_id,
888 tokio_id,
889 task_name.clone(),
890 thread_id,
891 );
892 } else {
893 let _ =
894 tracker.track_task_start_internal(task_id, None, task_name.clone(), thread_id);
895 }
896 }
897
898 crate::task_registry::global_registry()
903 .register_explicit_task_with_parent(task_id, &task_name, parent_id);
904
905 let mut lifecycle = TaskLifecycleGuard::new(task_id, tracker.clone());
910
911 let contextual = ContextualFuture::new(future, task_id);
913 let result = contextual.await;
914
915 lifecycle.set_completed();
918 result
919 })
920}
921
922pub fn is_tracking_active() -> bool {
924 GLOBAL_TRACKER.lock().is_ok_and(|global| global.is_some())
925}
926
927pub fn track_current_allocation(ptr: usize, size: usize) -> AsyncResult<()> {
929 let tracker = get_global_tracker()?;
930 let task_info = super::async_types::get_current_task();
931
932 if task_info.has_tracking_id() {
933 match u64::try_from(task_info.primary_id()) {
936 Ok(task_id) => tracker.track_allocation(ptr, size, task_id),
937 Err(_) => tracing::warn!(
938 target: "memscope::async",
939 task_id = %task_info.primary_id(),
940 ptr = format_args!("{ptr:#x}"),
941 "async task id does not fit into u64; allocation is not attributed"
942 ),
943 }
944 }
945
946 Ok(())
947}
948
949pub fn track_current_deallocation(ptr: usize) -> AsyncResult<()> {
951 let tracker = get_global_tracker()?;
952 tracker.track_deallocation(ptr);
953 Ok(())
954}
955
956#[cfg(test)]
957mod tests;