use std::collections::HashMap;
use std::future::Future;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use std::thread::ThreadId;
use super::async_types::{
AsyncAllocation, AsyncError, AsyncResult, AsyncSnapshot, AsyncStats, TrackedFuture,
};
use super::task_profile::{TaskMemoryProfile, TaskType};
pub fn generate_unique_task_id() -> u64 {
crate::task_registry::generate_task_id()
}
static THREAD_COUNTER: AtomicU64 = AtomicU64::new(1);
thread_local! {
static THREAD_ID: u64 = THREAD_COUNTER.fetch_add(1, Ordering::Relaxed);
}
pub fn current_thread_id() -> u64 {
THREAD_ID.with(|id| *id)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TrackerContext {
pub thread_id: u64,
pub task_id: Option<u64>,
pub tokio_task_id: Option<u64>,
}
impl TrackerContext {
pub fn capture() -> Self {
let _suppression = crate::core::allocator::TrackingGuard::acquire();
let task_id_from_context = TASK_CONTEXT.try_with(|ctx| *ctx).ok().flatten();
let tokio_task_id = tokio::task::try_id().and_then(|id| id.to_string().parse().ok());
Self {
thread_id: current_thread_id(),
task_id: task_id_from_context.or_else(AsyncTracker::get_current_task),
tokio_task_id,
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct TaskReport {
pub task_name: String,
pub task_type: TaskType,
pub efficiency_score: f64,
pub cpu_efficiency: f64,
pub memory_efficiency: f64,
pub io_efficiency: f64,
pub bottleneck: String,
pub recommendations: Vec<String>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ResourceRanking {
pub task_name: String,
pub task_type: TaskType,
pub cpu_usage: f64,
pub memory_usage_mb: f64,
pub io_usage_mb: f64,
pub network_usage_mb: f64,
pub gpu_usage: f64,
pub overall_score: f64,
}
static GLOBAL_TRACKER: Mutex<Option<Arc<AsyncTracker>>> = Mutex::new(None);
mod task_context;
pub use task_context::TaskGuard;
tokio::task_local! {
static TASK_CONTEXT: Option<u64>;
}
mod dealloc;
mod future;
mod queries;
use future::{ContextualFuture, TaskLifecycleGuard};
pub use queries::get_memory_snapshot;
fn numeric_thread_id(thread_id: ThreadId) -> Option<u64> {
if thread_id == std::thread::current().id() {
Some(crate::utils::current_thread_id_u64())
} else {
None
}
}
pub struct AsyncTracker {
allocations: Arc<Mutex<HashMap<usize, Vec<AsyncAllocation>>>>,
generations: Arc<Mutex<HashMap<usize, u64>>>,
stats: Arc<Mutex<AsyncStats>>,
profiles: Arc<Mutex<HashMap<u64, TaskMemoryProfile>>>,
initialized: Arc<Mutex<bool>>,
event_store: Option<crate::event_store::SharedEventStore>,
}
impl AsyncTracker {
pub(crate) fn lifecycle_profiles(&self) -> Vec<TaskMemoryProfile> {
let _guard = crate::core::allocator::TrackingGuard::acquire();
self.profiles
.lock()
.map(|p| p.values().cloned().collect())
.unwrap_or_default()
}
pub fn new() -> Self {
Self {
allocations: Arc::new(Mutex::new(HashMap::new())),
generations: Arc::new(Mutex::new(HashMap::new())),
stats: Arc::new(Mutex::new(AsyncStats::default())),
profiles: Arc::new(Mutex::new(HashMap::new())),
initialized: Arc::new(Mutex::new(false)),
event_store: None,
}
}
pub fn set_current_task(task_id: u64) {
task_context::set(task_id);
}
pub fn clear_current_task() {
task_context::clear();
}
pub fn get_current_task() -> Option<u64> {
task_context::current()
}
pub fn enter_task(task_id: u64) -> TaskGuard {
task_context::enter(task_id)
}
pub fn with_task<F, T>(task_id: u64, f: F) -> T
where
F: FnOnce() -> T,
{
let _guard = Self::enter_task(task_id);
f()
}
pub fn track_task_start(
&self,
task_id: u64,
name: String,
thread_id: ThreadId,
) -> Result<(), AsyncError> {
self.track_task_start_internal(task_id, None, name, thread_id)?;
Self::set_current_task(task_id);
Ok(())
}
pub fn track_task_start_with_tokio(
&self,
task_id: u64,
tokio_task_id: u64,
name: String,
thread_id: ThreadId,
) -> Result<(), AsyncError> {
self.track_task_start_internal(task_id, Some(tokio_task_id), name, thread_id)
}
fn track_task_start_internal(
&self,
task_id: u64,
tokio_task_id: Option<u64>,
name: String,
thread_id: ThreadId,
) -> Result<(), AsyncError> {
{
let mut profiles = self
.profiles
.lock()
.map_err(|e| AsyncError::mutex_lock_failed("profiles", &e.to_string()))?;
if profiles.contains_key(&task_id) {
return Err(AsyncError::duplicate_task(task_id));
}
let mut profile = match tokio_task_id {
Some(id) => {
TaskMemoryProfile::with_tokio_id(task_id, id, name, TaskType::default())
}
None => TaskMemoryProfile::new(task_id, name, TaskType::default()),
};
profile.set_thread_id_u64(numeric_thread_id(thread_id));
profiles.insert(task_id, profile);
}
let mut stats = self
.stats
.lock()
.map_err(|e| AsyncError::mutex_lock_failed("stats", &e.to_string()))?;
stats.total_tasks += 1;
stats.active_tasks += 1;
Ok(())
}
pub fn track_task_end(&self, task_id: u64) -> Result<(), AsyncError> {
{
let mut profiles = self
.profiles
.lock()
.map_err(|e| AsyncError::mutex_lock_failed("profiles", &e.to_string()))?;
let profile = profiles
.get_mut(&task_id)
.ok_or_else(|| AsyncError::task_not_found(task_id))?;
if profile.is_completed() {
return Ok(());
}
profile.mark_completed();
}
let mut stats = self
.stats
.lock()
.map_err(|e| AsyncError::mutex_lock_failed("stats", &e.to_string()))?;
stats.active_tasks = stats.active_tasks.saturating_sub(1);
task_context::end(task_id);
Ok(())
}
pub async fn track_in_tokio_task<F, T>(&self, name: String, future: F) -> (u64, T)
where
F: Future<Output = T>,
{
let unique_task_id = generate_unique_task_id();
let tokio_task_id = tokio::task::try_id().and_then(|id| id.to_string().parse().ok());
let thread_id = std::thread::current().id();
let parent_id = crate::task_registry::TaskIdRegistry::current_task_id();
if let Some(tokio_id) = tokio_task_id {
if let Err(e) =
self.track_task_start_with_tokio(unique_task_id, tokio_id, name.clone(), thread_id)
{
tracing::warn!("Failed to track task start: {e}");
}
} else if let Err(e) =
self.track_task_start_internal(unique_task_id, None, name.clone(), thread_id)
{
tracing::warn!("Failed to track task start: {e}");
}
crate::task_registry::global_registry().register_explicit_task_with_parent(
unique_task_id,
&name,
parent_id,
);
let lifecycle = future::BorrowedLifecycleGuard::new(self, unique_task_id);
let contextual = ContextualFuture::new(future, unique_task_id);
let output = contextual.await;
drop(lifecycle);
(unique_task_id, output)
}
pub fn detect_zombie_tasks(&self) -> Vec<u64> {
let _suppression = crate::core::allocator::TrackingGuard::acquire();
self.get_all_profiles()
.iter()
.filter(|p| !p.is_completed())
.map(|p| p.task_id)
.collect()
}
pub fn zombie_task_stats(&self) -> (usize, usize) {
let profiles = self.get_all_profiles();
let zombies = profiles.iter().filter(|p| !p.is_completed()).count();
let total = profiles.len();
(zombies, total)
}
pub fn track_allocation_auto(
&self,
ptr: usize,
size: usize,
var_name: Option<String>,
type_name: Option<String>,
) {
if let Some(task_id) = Self::get_current_task() {
self.track_allocation_with_location(ptr, size, task_id, var_name, type_name, None);
}
}
pub fn track_allocation(&self, ptr: usize, size: usize, task_id: u64) {
self.track_allocation_with_location(ptr, size, task_id, None, None, None);
}
pub fn track_allocation_with_location(
&self,
ptr: usize,
size: usize,
task_id: u64,
var_name: Option<String>,
type_name: Option<String>,
source_location: Option<super::async_types::SourceLocation>,
) {
let mut previous: Option<(u64, u64)> = None;
let mut generation = 1u64;
{
let Ok(mut allocations) = self.allocations.lock() else {
tracing::error!("Failed to acquire allocations lock during track_allocation");
return;
};
let entries = allocations.entry(ptr).or_default();
if let Some(existing) = entries.last_mut() {
if existing.size == size {
existing.var_name = var_name;
existing.type_name = type_name;
if source_location.is_some() {
existing.source_location = source_location;
}
return;
}
previous = Some((existing.task_id, existing.size as u64));
entries.pop();
}
if let Ok(mut generations) = self.generations.lock() {
let counter = generations.entry(ptr).or_insert(0);
*counter = counter.saturating_add(1);
generation = *counter;
}
entries.push(AsyncAllocation {
ptr,
size,
timestamp: Self::now(),
task_id,
var_name,
type_name,
source_location,
generation,
});
}
let size_u64 = size as u64;
if let Some((previous_task, previous_size)) = previous {
if let Ok(mut profiles) = self.profiles.lock() {
if let Some(profile) = profiles.get_mut(&previous_task) {
profile.record_deallocation(previous_size);
}
if let Some(profile) = profiles.get_mut(&task_id) {
profile.record_allocation(size_u64);
profile.set_thread_id_u64(Some(crate::utils::current_thread_id_u64()));
}
} else {
tracing::error!("Failed to acquire profiles lock during track_allocation");
}
if let Ok(mut stats) = self.stats.lock() {
stats.total_allocations += 1;
stats.total_memory += size;
stats.total_deallocated += previous_size;
stats.active_memory = stats
.active_memory
.saturating_sub(previous_size as usize)
.saturating_add(size);
if stats.active_memory > stats.peak_memory {
stats.peak_memory = stats.active_memory;
}
} else {
tracing::error!("Failed to acquire stats lock during track_allocation");
}
return;
}
if let Ok(mut profiles) = self.profiles.lock() {
if let Some(profile) = profiles.get_mut(&task_id) {
profile.record_allocation(size_u64);
profile.set_thread_id_u64(Some(crate::utils::current_thread_id_u64()));
}
} else {
tracing::error!("Failed to acquire profiles lock during track_allocation");
}
if let Ok(mut stats) = self.stats.lock() {
stats.total_allocations += 1;
stats.total_memory += size;
stats.active_memory += size;
if stats.active_memory > stats.peak_memory {
stats.peak_memory = stats.active_memory;
}
} else {
tracing::error!("Failed to acquire stats lock during track_allocation");
}
}
pub fn get_stats(&self) -> AsyncStats {
let _guard = crate::core::allocator::TrackingGuard::acquire();
if let Some((_, rebuilt)) = self.rebuilt() {
return queries::stats(&rebuilt);
}
if let Ok(stats) = self.stats.lock() {
stats.clone()
} else {
tracing::error!("Failed to acquire stats lock in get_stats");
AsyncStats::default()
}
}
pub fn snapshot(&self) -> AsyncSnapshot {
let _guard = crate::core::allocator::TrackingGuard::acquire();
if let Some((captured_at, rebuilt)) = self.rebuilt() {
return queries::snapshot(captured_at, rebuilt);
}
queries::local_snapshot(self)
}
pub fn get_task_profile(&self, task_id: u64) -> Option<TaskMemoryProfile> {
let _guard = crate::core::allocator::TrackingGuard::acquire();
if let Some((_, rebuilt)) = self.rebuilt() {
return rebuilt.profiles.into_iter().find(|p| p.task_id == task_id);
}
if let Ok(profiles) = self.profiles.lock() {
profiles.get(&task_id).cloned().map(|mut profile| {
profile.refresh_at(Self::now());
profile
})
} else {
tracing::error!("Failed to acquire profiles lock in get_task_profile");
None
}
}
pub fn get_all_profiles(&self) -> Vec<TaskMemoryProfile> {
let _guard = crate::core::allocator::TrackingGuard::acquire();
if let Some((_, rebuilt)) = self.rebuilt() {
return rebuilt.profiles;
}
if let Ok(profiles) = self.profiles.lock() {
let captured_at = Self::now();
profiles
.values()
.cloned()
.map(|mut profile| {
profile.refresh_at(captured_at);
profile
})
.collect()
} else {
tracing::error!("Failed to acquire profiles lock in get_all_profiles");
Vec::new()
}
}
pub fn is_initialized(&self) -> bool {
if let Ok(initialized) = self.initialized.lock() {
*initialized
} else {
tracing::error!("Failed to acquire initialized lock in is_initialized");
false
}
}
pub fn set_initialized(&self) {
if let Ok(mut initialized) = self.initialized.lock() {
*initialized = true;
} else {
tracing::error!("Failed to acquire initialized lock in set_initialized");
}
}
pub fn analyze_task(&self, task_id: u64, task_type: TaskType) -> Option<TaskReport> {
let _suppression = crate::core::allocator::TrackingGuard::acquire();
let profile = self.get_task_profile(task_id)?;
let total_bytes = profile.total_bytes as f64;
let total_allocations = profile.total_allocations as f64;
let peak_memory = profile.peak_memory as f64;
let duration_ms = profile.duration_ns as f64 / 1_000_000.0;
let compute_efficiency = if duration_ms > 0.0 {
(total_allocations / duration_ms * 1000.0).min(1.0)
} else {
0.0
};
let cpu_efficiency = match task_type {
TaskType::CpuIntensive | TaskType::IoIntensive | TaskType::GpuCompute => {
compute_efficiency
}
TaskType::MemoryIntensive => {
if total_bytes > 0.0 {
(peak_memory / total_bytes).min(1.0)
} else {
0.0
}
}
TaskType::NetworkIntensive => {
if total_bytes > 0.0 {
(total_allocations / total_bytes * 1000.0).min(1.0)
} else {
0.0
}
}
_ => compute_efficiency,
};
let memory_efficiency = if total_bytes > 0.0 {
(total_allocations / total_bytes * 1000.0).min(1.0)
} else {
0.0
};
let io_efficiency = if duration_ms > 0.0 {
(total_bytes / duration_ms / 1_048_576.0).min(1.0)
} else {
0.0
};
let efficiency_score = (cpu_efficiency + memory_efficiency + io_efficiency) / 3.0;
let bottleneck = if duration_ms > 5000.0 {
"Execution Time".to_string()
} else if peak_memory > 100.0 * 1024.0 * 1024.0 {
"Memory".to_string()
} else if total_allocations > 10000.0 {
"Allocations".to_string()
} else {
"None".to_string()
};
let mut recommendations = Vec::new();
if duration_ms > 5000.0 {
recommendations.push("Consider optimizing task execution time".to_string());
}
if peak_memory > 100.0 * 1024.0 * 1024.0 {
recommendations.push("Reduce peak memory usage".to_string());
}
if total_allocations > 10000.0 {
recommendations.push("Reduce number of allocations".to_string());
}
if recommendations.is_empty() {
recommendations.push("Performance is good".to_string());
}
Some(TaskReport {
task_name: profile.task_name.clone(),
task_type,
efficiency_score,
cpu_efficiency,
memory_efficiency,
io_efficiency,
bottleneck,
recommendations,
})
}
pub fn get_resource_rankings(&self) -> Vec<ResourceRanking> {
let _suppression = crate::core::allocator::TrackingGuard::acquire();
let profiles = self.get_all_profiles();
let mut rankings: Vec<ResourceRanking> = profiles
.into_iter()
.map(|profile| {
let memory_mb = profile.total_bytes as f64 / 1_048_576.0;
let peak_memory_mb = profile.peak_memory as f64 / 1_048_576.0;
let duration_ms = profile.duration_ns as f64 / 1_000_000.0;
let allocation_rate = profile.allocation_rate;
let overall_score = memory_mb * 0.3
+ peak_memory_mb * 0.2
+ allocation_rate * 0.0001
+ duration_ms * 0.0001;
ResourceRanking {
task_name: profile.task_name.clone(),
task_type: profile.task_type,
cpu_usage: allocation_rate,
memory_usage_mb: memory_mb,
io_usage_mb: 0.0,
network_usage_mb: 0.0,
gpu_usage: 0.0,
overall_score,
}
})
.collect();
rankings.sort_by(|a, b| {
b.overall_score
.partial_cmp(&a.overall_score)
.unwrap_or(std::cmp::Ordering::Equal)
});
rankings
}
fn now() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64
}
}
impl Default for AsyncTracker {
fn default() -> Self {
Self::new()
}
}
pub fn initialize() -> AsyncResult<()> {
let mut global = GLOBAL_TRACKER.lock().map_err(|_| AsyncError::System {
operation: Arc::from("initialize"),
message: Arc::from("Failed to acquire global tracker lock"),
})?;
if global.is_none() {
let tracker = AsyncTracker::new();
tracker.set_initialized();
*global = Some(Arc::new(tracker));
tracing::info!("Async memory tracking system initialized");
Ok(())
} else {
Err(AsyncError::initialization(
"tracker",
"Already initialized",
true,
))
}
}
pub fn shutdown() -> AsyncResult<()> {
let mut global = GLOBAL_TRACKER.lock().map_err(|_| AsyncError::System {
operation: Arc::from("shutdown"),
message: Arc::from("Failed to acquire global tracker lock"),
})?;
if global.is_some() {
*global = None;
tracing::info!("Async memory tracking system shutdown");
Ok(())
} else {
Err(AsyncError::initialization(
"tracker",
"Not initialized",
true,
))
}
}
#[cfg(test)]
pub fn reset_global_tracker() {
if let Ok(mut global) = GLOBAL_TRACKER.lock() {
*global = None;
} else {
tracing::error!("Failed to acquire global tracker lock in reset_global_tracker");
}
}
pub fn register_global(tracker: Arc<AsyncTracker>) -> AsyncResult<()> {
let mut global = GLOBAL_TRACKER.lock().map_err(|_| AsyncError::System {
operation: Arc::from("register_global"),
message: Arc::from("Failed to acquire global tracker lock"),
})?;
*global = Some(tracker);
Ok(())
}
fn get_global_tracker() -> AsyncResult<Arc<AsyncTracker>> {
GLOBAL_TRACKER
.lock()
.map_err(|_| AsyncError::System {
operation: Arc::from("get_global_tracker"),
message: Arc::from("Failed to acquire global tracker lock"),
})?
.clone()
.ok_or_else(|| {
AsyncError::initialization("tracker", "Tracking system not initialized", true)
})
}
pub fn notify_deallocation(ptr: usize) {
let Ok(tracker) = get_global_tracker() else {
return;
};
tracker.track_deallocation(ptr);
}
pub fn create_tracked<F>(future: F) -> TrackedFuture<F>
where
F: Future,
{
TrackedFuture::new(future)
}
pub fn spawn_tracked<F>(future: F) -> tokio::task::JoinHandle<F::Output>
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
let task_id = generate_unique_task_id();
let parent_id = crate::task_registry::TaskIdRegistry::current_task_id();
tokio::spawn(async move {
let tracker = get_global_tracker().ok();
let task_name = format!("spawned_task_{}", task_id);
if let Some(ref tracker) = tracker {
let thread_id = std::thread::current().id();
let tokio_id = tokio::task::try_id().and_then(|id| id.to_string().parse().ok());
if let Some(tokio_id) = tokio_id {
let _ = tracker.track_task_start_with_tokio(
task_id,
tokio_id,
task_name.clone(),
thread_id,
);
} else {
let _ =
tracker.track_task_start_internal(task_id, None, task_name.clone(), thread_id);
}
}
crate::task_registry::global_registry()
.register_explicit_task_with_parent(task_id, &task_name, parent_id);
let mut lifecycle = TaskLifecycleGuard::new(task_id, tracker.clone());
let contextual = ContextualFuture::new(future, task_id);
let result = contextual.await;
lifecycle.set_completed();
result
})
}
pub fn is_tracking_active() -> bool {
GLOBAL_TRACKER.lock().is_ok_and(|global| global.is_some())
}
pub fn track_current_allocation(ptr: usize, size: usize) -> AsyncResult<()> {
let tracker = get_global_tracker()?;
let task_info = super::async_types::get_current_task();
if task_info.has_tracking_id() {
match u64::try_from(task_info.primary_id()) {
Ok(task_id) => tracker.track_allocation(ptr, size, task_id),
Err(_) => tracing::warn!(
target: "memscope::async",
task_id = %task_info.primary_id(),
ptr = format_args!("{ptr:#x}"),
"async task id does not fit into u64; allocation is not attributed"
),
}
}
Ok(())
}
pub fn track_current_deallocation(ptr: usize) -> AsyncResult<()> {
let tracker = get_global_tracker()?;
tracker.track_deallocation(ptr);
Ok(())
}
#[cfg(test)]
mod tests;