use super::event_dto::{DashboardEventDTO, DataIndex, EventSummary};
use super::event_reconstructor::SamplingMetadata;
use super::evidence::{EvidenceLevel, RiskConfidence};
use super::inference::get_inferred_type_name;
use super::layout_inference::infer_layout_snapshot;
use super::types::*;
use crate::analysis::memory_passport_tracker::{PassportEventType, PassportStatus};
use crate::analysis::top_n::TopNAnalyzer;
use crate::analyzer::Analyzer;
use std::collections::HashMap;
pub fn build_allocation_info(
allocations: &[crate::capture::types::AllocationInfo],
) -> Vec<AllocationInfo> {
allocations
.iter()
.map(|a| {
let original_type_name = a.type_name.clone().unwrap_or_else(|| "unknown".to_string());
let type_name = get_inferred_type_name(&original_type_name, a.size);
let is_smart_pointer =
type_name.contains("Arc") || type_name.contains("Rc") || type_name.contains("Box");
let smart_pointer_type = if type_name.contains("Arc") {
"Arc".to_string()
} else if type_name.contains("Rc") {
"Rc".to_string()
} else if type_name.contains("Box") {
"Box".to_string()
} else {
String::new()
};
let allocation_state = match a.timestamp_dealloc {
Some(at_ns) => crate::snapshot::AllocationState::freed(at_ns),
None => crate::snapshot::AllocationState::live(),
};
let leak_assessment = crate::snapshot::LeakAssessment::not_assessed();
AllocationInfo {
address: format!("0x{:x}", a.ptr),
type_name: type_name.clone(),
size: a.size,
var_name: a.var_name.clone().unwrap_or_else(|| "unknown".to_string()),
timestamp: format!("{:?}", a.timestamp_alloc),
thread_id: format!("{:?}", a.thread_id_u64),
immutable_borrows: 0,
mutable_borrows: 0,
is_clone: a.clone_info.as_ref().map(|i| i.is_clone).unwrap_or(false),
clone_count: a.clone_info.as_ref().map(|i| i.clone_count).unwrap_or(0),
timestamp_alloc: a.timestamp_alloc,
timestamp_dealloc: a.timestamp_dealloc.unwrap_or(0),
lifetime_ms: calculate_lifetime_ms(a.timestamp_alloc, a.timestamp_dealloc),
observed_age_ms: None,
is_leaked: leak_assessment.is_reported_leak(),
allocation_state,
leak_status: leak_assessment.status,
allocation_type: if crate::analysis::is_virtual_pointer(a.ptr) {
"container".to_string()
} else if is_smart_pointer {
smart_pointer_type.clone()
} else {
"heap".to_string()
},
is_smart_pointer,
smart_pointer_type,
source_file: a
.stack_trace
.as_ref()
.and_then(|s| s.first())
.map(|s| s.split(':').next().unwrap_or("").to_string()),
source_line: a
.stack_trace
.as_ref()
.and_then(|s| s.first())
.and_then(|s| s.split(':').nth(1).and_then(|l| l.parse().ok())),
module_path: a.module_path.clone(),
generation_id: a.generation_id,
provenance: infer_provenance(a),
evidence: if a.type_name.is_some() {
EvidenceLevel::Observed
} else {
EvidenceLevel::Unknown
},
confidence: RiskConfidence::Unknown,
layout_snapshot: infer_layout_snapshot(&type_name, a.size, a.generation_id),
}
})
.collect()
}
fn calculate_lifetime_ms(timestamp_alloc: u64, timestamp_dealloc: Option<u64>) -> Option<f64> {
timestamp_dealloc?
.checked_sub(timestamp_alloc)
.map(|ns| ns as f64 / 1_000_000.0)
}
fn infer_provenance(a: &crate::capture::types::AllocationInfo) -> String {
if a.smart_pointer_info.is_some() {
return "smart_pointer".to_string();
}
if a.clone_info.as_ref().map(|c| c.is_clone).unwrap_or(false) {
return "clone".to_string();
}
if a.generation_id > 1 {
return "reused_address".to_string();
}
"allocator".to_string()
}
pub fn build_relationships(az: &mut Analyzer) -> Vec<RelationshipInfo> {
let graph_edges = az.graph().relationships();
let mut relationships: Vec<RelationshipInfo> = graph_edges
.iter()
.map(|edge| {
let (rel_type, color, strength) = match edge.relation {
crate::analyzer::Relation::Owns => ("ownership_transfer", "#dc2626", 1.0),
crate::analyzer::Relation::Contains => ("contains", "#f59e0b", 0.6),
crate::analyzer::Relation::Slice => ("immutable_borrow", "#3b82f6", 0.8),
crate::analyzer::Relation::Clone => ("clone", "#10b981", 0.9),
crate::analyzer::Relation::Shares => ("Arc", "#8b5cf6", 0.7),
crate::analyzer::Relation::Evolution => ("evolution", "#06b6d4", 0.5),
crate::analyzer::Relation::ArcClone => ("Arc_clone", "#8b5cf6", 0.7),
crate::analyzer::Relation::RcClone => ("Rc_clone", "#10b981", 0.9),
crate::analyzer::Relation::ImmutableBorrow => ("immutable_borrow", "#3b82f6", 0.8),
crate::analyzer::Relation::MutableBorrow => ("mutable_borrow", "#f59e0b", 0.9),
};
let type_name = edge
.from_type_name
.clone()
.unwrap_or_else(|| "unknown".to_string());
RelationshipInfo {
source_ptr: if edge.is_container_source {
format!("container_{}", edge.from_ptr)
} else {
format!("0x{:x}", edge.from_ptr)
},
source_var_name: edge.from_var_name.clone().unwrap_or_else(|| {
if edge.is_container_source {
format!("container_{}", edge.from_ptr)
} else {
format!("alloc_{}", edge.from_ptr)
}
}),
target_ptr: if edge.is_container_target {
format!("container_{}", edge.to_ptr)
} else {
format!("0x{:x}", edge.to_ptr)
},
target_var_name: edge.to_var_name.clone().unwrap_or_else(|| {
if edge.is_container_target {
format!("container_{}", edge.to_ptr)
} else {
format!("alloc_{}", edge.to_ptr)
}
}),
relationship_type: rel_type.to_string(),
strength,
type_name,
color: color.to_string(),
is_part_of_cycle: false,
is_container_source: edge.is_container_source,
is_container_target: edge.is_container_target,
}
})
.collect();
let cycle_edges: std::collections::HashSet<(String, String)> = {
let rel_tuples: Vec<(String, String, String)> = relationships
.iter()
.map(|r| {
(
r.source_ptr.clone(),
r.target_ptr.clone(),
r.type_name.clone(),
)
})
.collect();
let result = crate::analysis::detect_cycles_in_relationships(&rel_tuples);
result.cycle_edges
};
for rel in &mut relationships {
if cycle_edges.contains(&(rel.source_ptr.clone(), rel.target_ptr.clone())) {
rel.is_part_of_cycle = true;
rel.color = "#ef4444".to_string();
}
}
relationships
}
pub fn build_unsafe_reports(
passport_tracker: &crate::analysis::memory_passport_tracker::MemoryPassportTracker,
passports: &[crate::analysis::memory_passport_tracker::MemoryPassport],
all_allocations: &[crate::capture::types::AllocationInfo],
) -> Vec<UnsafeReport> {
passports
.iter()
.filter(|p| !p.lifecycle_events.is_empty())
.map(|p| build_unsafe_report(p, passport_tracker.assess_status(p), all_allocations))
.collect()
}
fn build_unsafe_report(
p: &crate::analysis::memory_passport_tracker::MemoryPassport,
status: PassportStatus,
all_allocations: &[crate::capture::types::AllocationInfo],
) -> UnsafeReport {
let all_allocations = if p.superseded { &[] } else { all_allocations };
let lifecycle_events = build_lifecycle_events(&p.lifecycle_events);
let cross_boundary_events = build_boundary_events(&lifecycle_events);
let is_leaked =
status == PassportStatus::InForeignCustody || status == PassportStatus::HandoverToFfi;
let risk_level = if is_leaked {
"high".to_string()
} else if !cross_boundary_events.is_empty() {
"medium".to_string()
} else {
"low".to_string()
};
let var_name = if p.var_name != "-" {
p.var_name.clone()
} else {
all_allocations
.iter()
.find(|a| a.ptr == p.allocation_ptr)
.and_then(|a| a.var_name.clone())
.unwrap_or_else(|| "-".to_string())
};
let type_name = if p.type_name != "-" {
p.type_name.clone()
} else {
let from_alloc = all_allocations
.iter()
.find(|a| a.ptr == p.allocation_ptr)
.and_then(|a| a.type_name.clone())
.unwrap_or_else(|| "-".to_string());
if from_alloc != "-" {
from_alloc
} else {
"unknown".to_string()
}
};
let mut risk_factors = Vec::new();
if is_leaked {
risk_factors.push("Memory leaked at shutdown".to_string());
}
if !cross_boundary_events.is_empty() {
risk_factors.push(format!(
"Crosses FFI boundary {} times",
cross_boundary_events.len()
));
}
if cross_boundary_events.len() > 3 {
risk_factors.push("Frequent boundary crossings".to_string());
}
let description = if risk_factors.is_empty() {
format!(
"{} ({} bytes) — no risk factors detected",
type_name, p.size_bytes
)
} else {
risk_factors.join("; ")
};
UnsafeReport {
passport_id: p.passport_id.clone(),
generation: p.generation,
superseded: p.superseded,
allocation_ptr: format!("0x{:x}", p.allocation_ptr),
var_name,
type_name,
size_bytes: p.size_bytes,
created_at: p.created_at,
updated_at: p.updated_at,
status: format!("{status:?}"),
lifecycle_events,
cross_boundary_events,
is_leaked,
risk_level,
risk_factors,
description,
}
}
pub fn build_passport_details(
passport_tracker: &crate::analysis::memory_passport_tracker::MemoryPassportTracker,
passports: &[crate::analysis::memory_passport_tracker::MemoryPassport],
all_allocations: &[crate::capture::types::AllocationInfo],
) -> Vec<PassportDetail> {
passports
.iter()
.map(|p| build_passport_detail(p, passport_tracker.assess_status(p), all_allocations))
.collect()
}
fn build_passport_detail(
p: &crate::analysis::memory_passport_tracker::MemoryPassport,
status: PassportStatus,
all_allocations: &[crate::capture::types::AllocationInfo],
) -> PassportDetail {
let all_allocations = if p.superseded { &[] } else { all_allocations };
let lifecycle_events = build_lifecycle_events(&p.lifecycle_events);
let cross_boundary_events = build_boundary_events(&lifecycle_events);
let var_name = if p.var_name != "-" {
p.var_name.clone()
} else {
all_allocations
.iter()
.find(|a| a.ptr == p.allocation_ptr)
.and_then(|a| a.var_name.clone())
.unwrap_or_else(|| "-".to_string())
};
let type_name = if p.type_name != "-" {
p.type_name.clone()
} else {
let from_alloc = all_allocations
.iter()
.find(|a| a.ptr == p.allocation_ptr)
.and_then(|a| a.type_name.clone())
.unwrap_or_else(|| "-".to_string());
if from_alloc != "-" {
from_alloc
} else {
"unknown".to_string()
}
};
let is_leaked =
status == PassportStatus::InForeignCustody || status == PassportStatus::HandoverToFfi;
let risk_level = if is_leaked {
"high".to_string()
} else if !cross_boundary_events.is_empty() {
"medium".to_string()
} else {
"low".to_string()
};
let is_active = !p.superseded
&& matches!(
status,
PassportStatus::StillLive | PassportStatus::ReclaimedByRust
);
let source_location = all_allocations
.iter()
.find(|a| a.ptr == p.allocation_ptr)
.and_then(|a| a.stack_trace.as_ref())
.and_then(|s| s.first())
.cloned()
.unwrap_or_else(|| format!("{} @ 0x{:x}", var_name, p.allocation_ptr));
PassportDetail {
passport_id: p.passport_id.clone(),
generation: p.generation,
superseded: p.superseded,
allocation_ptr: format!("0x{:x}", p.allocation_ptr),
var_name,
type_name,
size_bytes: p.size_bytes,
status: format!("{status:?}"),
created_at: p.created_at,
updated_at: p.updated_at,
is_leaked,
ffi_tracked: !cross_boundary_events.is_empty(),
lifecycle_events,
cross_boundary_events,
risk_level,
risk_confidence: 0.85,
is_active,
source_location,
}
}
pub fn build_lifecycle_events(
events: &[crate::analysis::memory_passport_tracker::PassportEvent],
) -> Vec<LifecycleEventInfo> {
events
.iter()
.map(|event| {
let (icon, color, context) = match &event.event_type {
PassportEventType::AllocatedInRust => (
"🟢".to_string(),
"#10b981".to_string(),
"Rust Allocation".to_string(),
),
PassportEventType::HandoverToFfi => (
"⬇️".to_string(),
"#f59e0b".to_string(),
"Handover to FFI".to_string(),
),
PassportEventType::FreedByForeign => (
"🔵".to_string(),
"#3b82f6".to_string(),
"Freed by Foreign".to_string(),
),
PassportEventType::ReclaimedByRust => (
"⬆️".to_string(),
"#10b981".to_string(),
"Reclaimed by Rust".to_string(),
),
PassportEventType::BoundaryAccess => (
"🔄".to_string(),
"#8b5cf6".to_string(),
"Boundary Access".to_string(),
),
PassportEventType::OwnershipTransfer => (
"↔️".to_string(),
"#dc2626".to_string(),
"Ownership Transfer".to_string(),
),
PassportEventType::ValidationCheck => (
"✅".to_string(),
"#10b981".to_string(),
"Validation Check".to_string(),
),
PassportEventType::CorruptionDetected => (
"🚨".to_string(),
"#dc2626".to_string(),
"Corruption Detected".to_string(),
),
};
LifecycleEventInfo {
event_type: format!("{:?}", event.event_type),
timestamp: event.timestamp,
context,
icon,
color,
}
})
.collect()
}
fn build_boundary_events(lifecycle_events: &[LifecycleEventInfo]) -> Vec<BoundaryEventInfo> {
lifecycle_events
.iter()
.filter(|e| e.event_type.contains("Handover") || e.event_type.contains("Reclaimed"))
.map(|e| {
let (event_type, from, to, icon, color) = if e.event_type.contains("HandoverToFfi") {
(
"RustToFfi".to_string(),
"Rust".to_string(),
"FFI".to_string(),
"⬇️".to_string(),
"#f59e0b".to_string(),
)
} else if e.event_type.contains("ReclaimedByRust") {
(
"FfiToRust".to_string(),
"FFI".to_string(),
"Rust".to_string(),
"⬆️".to_string(),
"#10b981".to_string(),
)
} else {
(
e.event_type.clone(),
"Unknown".to_string(),
"Unknown".to_string(),
"❓".to_string(),
"#6b7280".to_string(),
)
};
BoundaryEventInfo {
event_type,
from_context: from,
to_context: to,
timestamp: e.timestamp,
icon,
color,
}
})
.collect()
}
pub fn aggregate_thread_data(allocations: &[AllocationInfo]) -> Vec<ThreadInfo> {
let mut thread_map: HashMap<String, ThreadAggregator> = HashMap::new();
for alloc in allocations {
let entry = thread_map.entry(alloc.thread_id.clone()).or_default();
entry.allocation_count += 1;
entry.current_memory += alloc.size;
entry.total_allocated += alloc.size;
if alloc.size > entry.peak_memory {
entry.peak_memory = alloc.size;
}
}
thread_map
.into_iter()
.map(|(raw_tid, agg)| {
let summary = format!(
"{} allocs, {}",
agg.allocation_count,
super::helpers::format_bytes(agg.current_memory)
);
let thread_id = super::helpers::format_thread_id(&raw_tid);
let is_active = agg.allocation_count > 0;
let status = if is_active { "ACTIVE" } else { "IDLE" }.to_string();
ThreadInfo {
thread_id,
thread_summary: summary,
allocation_count: agg.allocation_count,
current_memory: super::helpers::format_bytes(agg.current_memory),
peak_memory: super::helpers::format_bytes(agg.peak_memory),
total_allocated: super::helpers::format_bytes(agg.total_allocated),
current_memory_bytes: agg.current_memory,
peak_memory_bytes: agg.peak_memory,
total_allocated_bytes: agg.total_allocated,
is_active,
status,
cpu_core: agg.cpu_core,
}
})
.collect()
}
pub fn aggregate_thread_data_from_ledger(
thread_stats: &HashMap<u64, crate::snapshot::types::ThreadMemoryStats>,
) -> Vec<ThreadInfo> {
thread_stats
.values()
.map(|stats| {
let thread_id = super::helpers::format_thread_id(&format!("{:?}", stats.thread_id));
ThreadInfo {
thread_summary: format!(
"{} allocs, {}",
stats.allocation_count,
super::helpers::format_bytes(stats.current_memory)
),
thread_id,
allocation_count: stats.allocation_count,
current_memory: super::helpers::format_bytes(stats.current_memory),
peak_memory: super::helpers::format_bytes(stats.peak_memory),
total_allocated: super::helpers::format_bytes(stats.total_allocated),
current_memory_bytes: stats.current_memory,
peak_memory_bytes: stats.peak_memory,
total_allocated_bytes: stats.total_allocated,
is_active: stats.allocation_count > 0,
status: if stats.allocation_count > 0 {
"ACTIVE".to_string()
} else {
"IDLE".to_string()
},
cpu_core: None,
}
})
.collect()
}
pub fn build_async_tasks_from_rebuilt(
rebuilt: &crate::capture::backends::RebuiltProfiles,
) -> Vec<AsyncTaskInfo> {
rebuilt
.profiles
.iter()
.map(|p| {
let is_completed = p.is_completed();
let has_potential_leak = p.has_potential_leak();
let task_type_str = format!("{:?}", p.task_type);
let status = if has_potential_leak {
"LEAKED".to_string()
} else if is_completed {
"COMPLETED".to_string()
} else {
"RUNNING".to_string()
};
AsyncTaskInfo {
task_id: p.task_id,
created_at_ms: p.created_at_ms,
completed_at_ms: p.completed_at_ms,
task_name: p.task_name.clone(),
task_type: task_type_str,
total_bytes: p.total_bytes,
current_memory: p.current_memory,
peak_memory: p.peak_memory,
total_allocations: p.total_allocations,
duration_ms: p.duration_ns as f64 / 1_000_000.0,
efficiency_score: p.efficiency_score,
is_completed,
has_potential_leak,
status,
}
})
.collect()
}
pub fn build_async_summary_from_rebuilt(
rebuilt: &crate::capture::backends::RebuiltProfiles,
async_tasks: &[AsyncTaskInfo],
) -> AsyncSummary {
let total_tasks = rebuilt.profiles.len();
let active_tasks = rebuilt
.profiles
.iter()
.filter(|p| !p.is_completed())
.count();
let completed = async_tasks.iter().filter(|t| t.is_completed).count();
let leaked = async_tasks.iter().filter(|t| t.has_potential_leak).count();
let zombie = async_tasks
.iter()
.filter(|t| !t.is_completed && !t.has_potential_leak)
.count();
let success_rate = if total_tasks > 0 {
(completed as f64 / total_tasks as f64) * 100.0
} else {
0.0
};
AsyncSummary {
total_tasks,
active_tasks,
total_allocations: rebuilt.total_allocations as usize,
total_memory_bytes: rebuilt.total_memory as usize,
peak_memory_bytes: rebuilt.global_peak_memory as usize,
completed,
leaked,
zombie,
success_rate,
}
}
pub fn build_ownership_graph_info(
allocations: &[crate::capture::types::AllocationInfo],
) -> OwnershipGraphInfo {
let total_nodes = allocations.len();
OwnershipGraphInfo {
total_nodes,
total_edges: 0,
total_cycles: 0,
rc_clone_count: 0,
arc_clone_count: 0,
has_issues: false,
issues: vec![],
root_cause: None,
}
}
pub fn build_top_n_reports(allocations: &[crate::capture::types::AllocationInfo]) -> TopNReports {
let converted_allocations = allocations.to_vec();
let analyzer = TopNAnalyzer::new(converted_allocations);
let top_allocation_sites = analyzer
.top_allocation_sites(10)
.iter()
.map(|site| TopAllocationSite {
name: site.name.clone(),
total_bytes: site.total_bytes,
allocation_count: site.allocation_count,
})
.collect();
let top_leaked_allocations = analyzer
.top_leaked_bytes(10)
.iter()
.map(|leaked| TopLeakedAllocation {
address: format!("0x{:x}", leaked.ptr),
size: leaked.size,
type_name: leaked
.type_name
.clone()
.unwrap_or_else(|| "unknown".to_string()),
timestamp_alloc: leaked.timestamp_alloc,
stack_trace: leaked.stack_trace.clone(),
})
.collect();
let top_temporary_churn = analyzer
.top_temporary_churn(10, 100)
.iter()
.map(|churn| TopTemporaryChurn {
name: churn.name.clone(),
allocation_count: churn.allocation_count,
total_bytes: churn.total_bytes,
average_lifetime_ms: churn.average_lifetime_ms,
})
.collect();
TopNReports {
top_allocation_sites,
top_leaked_allocations,
top_temporary_churn,
}
}
pub struct TopNReports {
pub top_allocation_sites: Vec<TopAllocationSite>,
pub top_leaked_allocations: Vec<TopLeakedAllocation>,
pub top_temporary_churn: Vec<TopTemporaryChurn>,
}
pub fn build_circular_reference_report(
allocations: &[crate::capture::types::AllocationInfo],
) -> CircularReferenceReport {
let analysis = crate::analysis::circular_reference::detect_circular_references(allocations);
CircularReferenceReport {
count: analysis.circular_references.len(),
total_leaked_memory: analysis.total_leaked_memory,
pointers_in_cycles: analysis.pointers_in_cycles,
total_smart_pointers: analysis.total_smart_pointers,
has_cycles: !analysis.circular_references.is_empty(),
}
}
pub fn calculate_health_info(
unsafe_reports: &[UnsafeReport],
passport_details: &[PassportDetail],
leak_count: usize,
total_allocs: usize,
) -> HealthInfo {
let high_risk_count = unsafe_reports
.iter()
.filter(|r| r.risk_level == "high")
.count();
let clean_passport_count = passport_details.iter().filter(|p| !p.is_leaked).count();
let active_passport_count = passport_details
.iter()
.filter(|p| p.status == "active")
.count();
let leaked_passport_count = passport_details.iter().filter(|p| p.is_leaked).count();
let ffi_tracked_count = passport_details.iter().filter(|p| p.ffi_tracked).count();
let total_allocs = total_allocs.max(1);
let unsafe_count = unsafe_reports.len();
let high_risk_penalty = 10.0;
let leak_score = (100.0 - (leak_count as f64 / total_allocs as f64) * 100.0).max(0.0);
let unsafe_score = (100.0 - (unsafe_count as f64 / total_allocs as f64) * 50.0).max(0.0);
let risk_score = (100.0 - high_risk_count as f64 * high_risk_penalty).max(0.0);
let health_score = ((leak_score + unsafe_score + risk_score) / 3.0).round() as u32;
let health_status = if health_score >= 80 {
"✅ Excellent"
} else if health_score >= 60 {
"⚠️ Good"
} else {
"🚨 Needs Attention"
};
let safe_ops_count = total_allocs.saturating_sub(unsafe_count);
let safe_code_percent = ((safe_ops_count as f64 / total_allocs as f64) * 100.0).round() as u32;
HealthInfo {
health_score,
health_status: health_status.to_string(),
safe_ops_count,
high_risk_count,
clean_passport_count,
active_passport_count,
leaked_passport_count,
ffi_tracked_count,
safe_code_percent,
}
}
pub struct HealthInfo {
pub health_score: u32,
pub health_status: String,
pub safe_ops_count: usize,
pub high_risk_count: usize,
pub clean_passport_count: usize,
pub active_passport_count: usize,
pub leaked_passport_count: usize,
pub ffi_tracked_count: usize,
pub safe_code_percent: u32,
}
#[allow(clippy::too_many_arguments, dead_code)]
pub fn build_json_data(
alloc_info: &[super::types::AllocationInfo],
relationships: &[super::types::RelationshipInfo],
unsafe_reports: &[super::types::UnsafeReport],
thread_data: &[super::types::ThreadInfo],
passport_details: &[super::types::PassportDetail],
active_allocations: usize,
total_allocations: usize,
leak_count: usize,
async_tasks: &[super::types::AsyncTaskInfo],
async_summary: &super::types::AsyncSummary,
ownership_graph: &super::types::OwnershipGraphInfo,
health_score: u32,
task_graph_json: &str,
events: &[DashboardEventDTO],
event_summary: &EventSummary,
data_index: &DataIndex,
sampling: &SamplingMetadata,
) -> Result<String, Box<dyn std::error::Error>> {
#[derive(serde::Serialize)]
struct DashboardData<'a> {
allocations: &'a [super::types::AllocationInfo],
relationships: &'a [super::types::RelationshipInfo],
unsafe_reports: &'a [super::types::UnsafeReport],
threads: &'a [super::types::ThreadInfo],
passport_details: &'a [super::types::PassportDetail],
active_allocations: usize,
total_allocations: usize,
leak_count: usize,
async_tasks: &'a [super::types::AsyncTaskInfo],
async_summary: &'a super::types::AsyncSummary,
ownership_graph: &'a super::types::OwnershipGraphInfo,
health_score: u32,
task_graph_json: &'a str,
events: &'a [DashboardEventDTO],
event_summary: &'a EventSummary,
data_index: &'a DataIndex,
sampling: &'a SamplingMetadata,
}
let data = DashboardData {
allocations: alloc_info,
relationships,
unsafe_reports,
threads: thread_data,
passport_details,
active_allocations,
total_allocations,
leak_count,
async_tasks,
async_summary,
ownership_graph,
health_score,
task_graph_json,
events,
event_summary,
data_index,
sampling,
};
Ok(serde_json::to_string(&data)?)
}
#[cfg(test)]
mod tests;