mod timeline;
pub use timeline::*;
use scirs2_core::numeric::Float;
use std::collections::{HashMap, VecDeque};
use std::fmt::Debug;
use std::fs::File;
use std::io::Write;
use std::sync::{Arc, Mutex, RwLock};
use std::time::{Duration, Instant, SystemTime};
use super::super::frontend::XLAComputation;
use super::BackendConfig;
use crate::error::{OptimError, Result};
pub struct ProfilingIntegration<T> {
config: ProfilingConfig,
counter_manager: PerformanceCounterManager,
trace_collector: TraceCollector,
memory_profiler: MemoryProfiler,
timeline_profiler: TimelineProfiler<T>,
export_manager: ProfileExportManager,
profiling_stats: ProfilingStatistics,
}
#[derive(Debug, Clone)]
pub struct ProfilingConfig {
pub enable_perf_counters: bool,
pub enable_trace_collection: bool,
pub enable_memory_profiling: bool,
pub enable_power_profiling: bool,
pub enable_timeline_profiling: bool,
pub sampling_rate: u64,
pub max_trace_buffer_mb: usize,
pub output_directory: String,
pub export_format: ExportFormat,
pub detailed_mode: bool,
}
#[derive(Debug, Clone)]
pub enum ExportFormat {
JSON,
ProtoBuf,
ChromeTrace,
CSV,
Binary,
}
#[derive(Debug, Default)]
pub struct ProfilingStatistics {
pub samples_collected: u64,
pub trace_events: u64,
pub memory_snapshots: u64,
pub power_samples: u64,
pub profiling_overhead_us: u64,
pub export_time_us: u64,
}
pub struct PerformanceCounterManager {
available_counters: HashMap<String, CounterInfo>,
active_sessions: HashMap<String, CounterSession>,
counter_data: Arc<RwLock<HashMap<String, CounterTimeSeries>>>,
}
#[derive(Debug, Clone)]
pub struct CounterInfo {
pub name: String,
pub description: String,
pub counter_type: CounterType,
pub units: String,
pub granularity: CounterGranularity,
pub hardware_dependency: Option<String>,
}
#[derive(Debug, Clone)]
pub enum CounterType {
Cumulative,
Gauge,
Rate,
Histogram,
}
#[derive(Debug, Clone)]
pub enum CounterGranularity {
Instruction,
Operation,
Kernel,
Execution,
System,
}
#[derive(Debug)]
pub struct CounterSession {
pub id: String,
pub start_time: Instant,
pub enabled_counters: Vec<String>,
pub sample_buffer: VecDeque<CounterSample>,
pub config: SessionConfig,
}
#[derive(Debug, Clone)]
pub struct CounterSample {
pub timestamp: Instant,
pub counter_name: String,
pub value: CounterValue,
pub context: Option<String>,
}
#[derive(Debug, Clone)]
pub enum CounterValue {
Integer(i64),
Float(f64),
Boolean(bool),
String(String),
Histogram(Vec<(f64, u64)>),
}
#[derive(Debug)]
pub struct CounterTimeSeries {
pub counter_name: String,
pub samples: Vec<(Instant, CounterValue)>,
pub statistics: TimeSeriesStats,
}
#[derive(Debug, Default)]
pub struct TimeSeriesStats {
pub min: f64,
pub max: f64,
pub average: f64,
pub std_dev: f64,
pub sample_count: usize,
}
#[derive(Debug, Clone)]
pub struct SessionConfig {
pub sampling_interval_us: u64,
pub buffer_size: usize,
pub auto_flush_threshold: usize,
pub include_context: bool,
}
#[derive(Debug)]
pub struct CounterConfig {
pub default_sampling_rate: u64,
pub counter_groups: HashMap<String, Vec<String>>,
pub aliases: HashMap<String, String>,
}
pub struct TraceCollector {
trace_buffer: Arc<Mutex<TraceBuffer>>,
trace_sessions: HashMap<String, TraceSession>,
}
#[derive(Debug)]
pub struct TraceBuffer {
pub events: VecDeque<TraceEvent>,
pub max_size: usize,
pub current_size: usize,
pub stats: BufferStats,
}
#[derive(Debug, Clone)]
pub struct TraceEvent {
pub id: u64,
pub timestamp: Instant,
pub event_type: EventType,
pub phase: EventPhase,
pub thread_id: Option<u64>,
pub process_id: Option<u64>,
pub name: String,
pub category: String,
pub duration: Option<Duration>,
pub args: HashMap<String, String>,
pub stack_trace: Option<Vec<String>>,
}
#[derive(Debug, Clone)]
pub enum EventType {
FunctionCall,
KernelExecution,
MemoryOperation,
Communication,
Synchronization,
ResourceAllocation,
Custom(String),
}
#[derive(Debug, Clone)]
pub enum EventPhase {
Begin,
End,
Instant,
Complete,
AsyncBegin,
AsyncEnd,
}
#[derive(Debug)]
pub struct TraceSession {
pub id: String,
pub start_time: Instant,
pub enabled_events: Vec<EventType>,
pub session_buffer: Vec<TraceEvent>,
pub metadata: TraceMetadata,
}
#[derive(Debug, Default)]
pub struct TraceMetadata {
pub session_name: String,
pub target_executable: Option<String>,
pub hardware_info: HashMap<String, String>,
pub software_info: HashMap<String, String>,
}
#[derive(Debug)]
pub struct EventFilter {
pub name: String,
pub event_type_filter: Option<EventType>,
pub category_filter: Option<String>,
pub duration_threshold: Option<Duration>,
pub include: bool,
}
#[derive(Debug, Default)]
pub struct BufferStats {
pub events_written: u64,
pub events_dropped: u64,
pub overruns: u64,
pub peak_usage: usize,
}
#[derive(Debug)]
pub struct TraceConfig {
pub buffer_size: usize,
pub include_stack_traces: bool,
pub max_stack_depth: usize,
pub enable_compression: bool,
}
pub struct MemoryProfiler {
tracking_sessions: HashMap<String, MemoryTrackingSession>,
allocation_tracker: AllocationTracker,
usage_snapshots: Vec<MemorySnapshot>,
}
#[derive(Debug)]
pub struct MemoryTrackingSession {
pub id: String,
pub start_time: Instant,
pub allocations: HashMap<usize, AllocationInfo>,
pub stats: MemoryTrackingStats,
}
#[derive(Debug)]
pub struct AllocationInfo {
pub address: usize,
pub size: usize,
pub timestamp: Instant,
pub source: AllocationSource,
pub stack_trace: Option<Vec<String>>,
pub tags: Vec<String>,
}
#[derive(Debug)]
pub enum AllocationSource {
Kernel(String),
Runtime,
User,
Unknown,
}
#[derive(Debug, Default)]
pub struct MemoryTrackingStats {
pub total_allocations: usize,
pub total_deallocations: usize,
pub current_allocations: usize,
pub peak_memory_usage: usize,
pub current_memory_usage: usize,
pub fragmentation_ratio: f64,
}
pub struct AllocationTracker {
active_allocations: HashMap<usize, AllocationInfo>,
allocation_history: Vec<AllocationEvent>,
}
#[derive(Debug)]
pub struct AllocationEvent {
pub timestamp: Instant,
pub event_type: AllocationEventType,
pub address: usize,
pub size: usize,
pub context: Option<String>,
}
#[derive(Debug)]
pub enum AllocationEventType {
Allocate,
Deallocate,
Reallocate,
}
#[derive(Debug)]
pub struct TrackerConfig {
pub track_stack_traces: bool,
pub max_history_size: usize,
pub enable_leak_detection: bool,
}
#[derive(Debug)]
pub struct MemorySnapshot {
pub timestamp: Instant,
pub regions: Vec<MemoryRegion>,
pub total_usage: usize,
pub fragmentation: FragmentationInfo,
}
#[derive(Debug)]
pub struct MemoryRegion {
pub start_address: usize,
pub size: usize,
pub region_type: MemoryRegionType,
pub usage: RegionUsage,
}
#[derive(Debug)]
pub enum MemoryRegionType {
Code,
Data,
Stack,
Heap,
Device,
}
#[derive(Debug)]
pub struct RegionUsage {
pub used_bytes: usize,
pub free_bytes: usize,
pub fragmentation: f64,
}
#[derive(Debug, Default)]
pub struct FragmentationInfo {
pub external_fragmentation: f64,
pub internal_fragmentation: f64,
pub largest_free_block: usize,
pub free_block_count: usize,
}
#[derive(Debug)]
pub struct MemoryProfilingConfig {
pub snapshot_interval_ms: u64,
pub track_allocations: bool,
pub max_snapshots: usize,
pub enable_heap_profiling: bool,
}
#[derive(Debug)]
pub struct PowerMonitoringSession {
pub id: String,
pub start_time: Instant,
pub components: Vec<PowerComponent>,
pub samples: Vec<PowerSample>,
}
#[derive(Debug, Clone)]
pub enum PowerComponent {
CPU,
TPU,
Memory,
Interconnect,
System,
}
#[derive(Debug, Clone)]
pub struct PowerSample {
pub timestamp: Instant,
pub component: PowerComponent,
pub power_watts: f64,
pub voltage: Option<f64>,
pub current: Option<f64>,
pub temperature: Option<f64>,
}
#[derive(Debug)]
pub struct PowerProfilingConfig {
pub sampling_rate: u64,
pub component_level_monitoring: bool,
pub include_thermal: bool,
pub model_accuracy: PowerModelAccuracy,
}
#[derive(Debug)]
pub enum PowerModelAccuracy {
Low,
Medium,
High,
}
#[derive(Debug)]
pub struct ComponentPowerModel {
pub base_power: f64,
pub dynamic_factors: HashMap<String, f64>,
pub thermal_coefficients: Vec<f64>,
}
#[derive(Debug, Default)]
pub struct AggregatedMetrics {
pub performance: PerformanceMetrics,
pub memory: MemoryMetrics,
pub power: PowerMetrics,
pub timeline: TimelineMetrics,
}
#[derive(Debug, Default)]
pub struct PerformanceMetrics {
pub avg_execution_time_us: f64,
pub throughput: f64,
pub compute_utilization: f64,
pub memory_bandwidth_util: f64,
}
#[derive(Debug, Default)]
pub struct MemoryMetrics {
pub peak_usage_bytes: usize,
pub avg_usage_bytes: f64,
pub efficiency: f64,
pub allocation_rate: f64,
}
#[derive(Debug, Default)]
pub struct PowerMetrics {
pub avg_power_watts: f64,
pub peak_power_watts: f64,
pub energy_joules: f64,
pub efficiency: f64,
}
#[derive(Debug, Default)]
pub struct TimelineMetrics {
pub total_operations: usize,
pub avg_operation_duration_us: f64,
pub critical_path_length_us: u64,
pub parallelization_efficiency: f64,
}
#[derive(Debug)]
pub struct AggregationConfig {
pub interval_seconds: u64,
pub real_time: bool,
pub retention_hours: u32,
}
pub struct ProfileExportManager {
export_config: ExportConfig,
export_stats: ExportStatistics,
}
#[derive(Debug)]
pub struct ExportConfig {
pub format: ExportFormat,
pub output_dir: String,
pub include_raw_data: bool,
pub compression: bool,
pub include_metadata: bool,
}
#[derive(Debug, Default)]
pub struct ExportStatistics {
pub files_exported: usize,
pub total_size_bytes: usize,
pub export_time_us: u64,
pub compression_ratio: f64,
}
impl<T: Float + Debug + Send + Sync + 'static> ProfilingIntegration<T> {
pub fn new(config: &BackendConfig) -> Self {
let profiling_config = ProfilingConfig {
enable_perf_counters: config.enable_profiling,
enable_trace_collection: config.enable_profiling,
enable_memory_profiling: config.enable_profiling,
enable_power_profiling: false,
enable_timeline_profiling: config.enable_profiling,
sampling_rate: 1000, max_trace_buffer_mb: 100,
output_directory: "/tmp/scirs_profiles".to_string(),
export_format: ExportFormat::JSON,
detailed_mode: config.debug_mode,
};
Self {
counter_manager: PerformanceCounterManager::new(),
trace_collector: TraceCollector::new(&profiling_config),
memory_profiler: MemoryProfiler::new(&profiling_config),
timeline_profiler: TimelineProfiler::new(&profiling_config),
export_manager: ProfileExportManager::new(&profiling_config),
config: profiling_config,
profiling_stats: ProfilingStatistics::default(),
}
}
pub fn setup_profiling(
&mut self,
_computation: &XLAComputation<T>,
_binary: &[u8],
) -> Result<()> {
if self.config.enable_perf_counters {
self.counter_manager.start_session("main_session")?;
}
if self.config.enable_trace_collection {
self.trace_collector.start_tracing("main_trace")?;
}
if self.config.enable_memory_profiling {
self.memory_profiler.start_tracking("main_memory")?;
}
if self.config.enable_timeline_profiling {
self.timeline_profiler.start_timeline("main_timeline")?;
}
Ok(())
}
pub fn record_compile_timings(&mut self, codegen: Duration, runtime_integration: Duration) {
if self.config.enable_perf_counters {
self.counter_manager.record_sample(
"main_session",
"codegen_time_us",
CounterValue::Integer(codegen.as_micros() as i64),
);
self.counter_manager.record_sample(
"main_session",
"runtime_integration_time_us",
CounterValue::Integer(runtime_integration.as_micros() as i64),
);
self.profiling_stats.samples_collected += 2;
}
if self.config.enable_trace_collection {
self.trace_collector.record_event(TraceEvent {
id: 0, timestamp: Instant::now(),
event_type: EventType::FunctionCall,
phase: EventPhase::Complete,
thread_id: None,
process_id: None,
name: "codegen".to_string(),
category: "compile".to_string(),
duration: Some(codegen),
args: HashMap::new(),
stack_trace: None,
});
self.trace_collector.record_event(TraceEvent {
id: 0,
timestamp: Instant::now(),
event_type: EventType::FunctionCall,
phase: EventPhase::Complete,
thread_id: None,
process_id: None,
name: "runtime_integration".to_string(),
category: "compile".to_string(),
duration: Some(runtime_integration),
args: HashMap::new(),
stack_trace: None,
});
self.profiling_stats.trace_events += 2;
}
}
pub fn export_data(&mut self) -> Result<Vec<String>> {
let mut exported_files = Vec::new();
if self.config.enable_perf_counters {
let file_path = self
.export_manager
.export_counter_data(&self.counter_manager)?;
exported_files.push(file_path);
}
if self.config.enable_trace_collection {
let file_path = self
.export_manager
.export_trace_data(&self.trace_collector)?;
exported_files.push(file_path);
}
if self.config.enable_memory_profiling {
let file_path = self
.export_manager
.export_memory_data(&self.memory_profiler)?;
exported_files.push(file_path);
}
Ok(exported_files)
}
pub fn record_memory_allocation(
&mut self,
session_id: &str,
address: usize,
size: usize,
context: Option<String>,
) {
if !self.config.enable_memory_profiling {
return;
}
self.memory_profiler.record_allocation(
session_id,
address,
size,
AllocationSource::Runtime,
context,
);
}
pub fn capture_memory_snapshot(&mut self, fragmentation: FragmentationInfo) {
if !self.config.enable_memory_profiling {
return;
}
self.memory_profiler.capture_snapshot(fragmentation);
}
pub fn record_memory_release(
&mut self,
session_id: &str,
addresses: &[usize],
fragmentation: FragmentationInfo,
context: Option<String>,
) {
if !self.config.enable_memory_profiling {
return;
}
for &address in addresses {
self.memory_profiler
.record_deallocation(session_id, address, context.clone());
}
self.memory_profiler.capture_snapshot(fragmentation);
}
pub fn memory_profiler(&self) -> &MemoryProfiler {
&self.memory_profiler
}
pub fn reset(&mut self) {
self.profiling_stats = ProfilingStatistics::default();
self.counter_manager.reset();
self.trace_collector.reset();
self.memory_profiler.reset();
self.timeline_profiler.reset();
}
}
impl Default for PerformanceCounterManager {
fn default() -> Self {
Self::new()
}
}
impl PerformanceCounterManager {
pub fn new() -> Self {
let mut available_counters = HashMap::new();
available_counters.insert(
"matrix_ops".to_string(),
CounterInfo {
name: "matrix_ops".to_string(),
description: "Matrix operations executed".to_string(),
counter_type: CounterType::Cumulative,
units: "operations".to_string(),
granularity: CounterGranularity::Operation,
hardware_dependency: Some("matrix_unit".to_string()),
},
);
available_counters.insert(
"memory_bandwidth".to_string(),
CounterInfo {
name: "memory_bandwidth".to_string(),
description: "Memory bandwidth utilization".to_string(),
counter_type: CounterType::Gauge,
units: "GB/s".to_string(),
granularity: CounterGranularity::System,
hardware_dependency: Some("memory_controller".to_string()),
},
);
Self {
available_counters,
active_sessions: HashMap::new(),
counter_data: Arc::new(RwLock::new(HashMap::new())),
}
}
pub fn start_session(&mut self, session_id: &str) -> Result<()> {
let session = CounterSession {
id: session_id.to_string(),
start_time: Instant::now(),
enabled_counters: self.available_counters.keys().cloned().collect(),
sample_buffer: VecDeque::new(),
config: SessionConfig {
sampling_interval_us: 1000, buffer_size: 10000,
auto_flush_threshold: 8000,
include_context: true,
},
};
self.active_sessions.insert(session_id.to_string(), session);
Ok(())
}
pub fn record_sample(&mut self, session_id: &str, counter_name: &str, value: CounterValue) {
let now = Instant::now();
self.active_sessions
.entry(session_id.to_string())
.or_insert_with(|| CounterSession {
id: session_id.to_string(),
start_time: now,
enabled_counters: Vec::new(),
sample_buffer: VecDeque::new(),
config: SessionConfig {
sampling_interval_us: 1000,
buffer_size: 10000,
auto_flush_threshold: 8000,
include_context: true,
},
})
.sample_buffer
.push_back(CounterSample {
timestamp: now,
counter_name: counter_name.to_string(),
value: value.clone(),
context: None,
});
let mut data = self
.counter_data
.write()
.unwrap_or_else(|poisoned| poisoned.into_inner());
let series = data
.entry(counter_name.to_string())
.or_insert_with(|| CounterTimeSeries {
counter_name: counter_name.to_string(),
samples: Vec::new(),
statistics: TimeSeriesStats::default(),
});
series.samples.push((now, value));
recompute_time_series_statistics(series);
}
pub fn reset(&mut self) {
self.active_sessions.clear();
let mut data = self
.counter_data
.write()
.unwrap_or_else(|poisoned| poisoned.into_inner());
data.clear();
}
}
fn recompute_time_series_statistics(series: &mut CounterTimeSeries) {
let values: Vec<f64> = series
.samples
.iter()
.filter_map(|(_, v)| match v {
CounterValue::Integer(i) => Some(*i as f64),
CounterValue::Float(f) => Some(*f),
CounterValue::Boolean(_) | CounterValue::String(_) | CounterValue::Histogram(_) => None,
})
.collect();
series.statistics.sample_count = series.samples.len();
if values.is_empty() {
series.statistics.min = 0.0;
series.statistics.max = 0.0;
series.statistics.average = 0.0;
series.statistics.std_dev = 0.0;
return;
}
let min = values.iter().cloned().fold(f64::INFINITY, f64::min);
let max = values.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
let sum: f64 = values.iter().sum();
let average = sum / values.len() as f64;
let variance = values.iter().map(|v| (v - average).powi(2)).sum::<f64>() / values.len() as f64;
series.statistics.min = min;
series.statistics.max = max;
series.statistics.average = average;
series.statistics.std_dev = variance.sqrt();
}
impl TraceCollector {
pub fn new(config: &ProfilingConfig) -> Self {
Self {
trace_buffer: Arc::new(Mutex::new(TraceBuffer {
events: VecDeque::new(),
max_size: config.max_trace_buffer_mb * 1024 * 1024,
current_size: 0,
stats: BufferStats::default(),
})),
trace_sessions: HashMap::new(),
}
}
pub fn start_tracing(&mut self, session_id: &str) -> Result<()> {
let session = TraceSession {
id: session_id.to_string(),
start_time: Instant::now(),
enabled_events: vec![EventType::KernelExecution, EventType::MemoryOperation],
session_buffer: Vec::new(),
metadata: TraceMetadata::default(),
};
self.trace_sessions.insert(session_id.to_string(), session);
Ok(())
}
pub fn record_event(&mut self, mut event: TraceEvent) {
let mut buffer = self
.trace_buffer
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
event.id = buffer.stats.events_written;
let event_size =
std::mem::size_of::<TraceEvent>() + event.name.len() + event.category.len();
if buffer.current_size + event_size > buffer.max_size && !buffer.events.is_empty() {
if let Some(dropped) = buffer.events.pop_front() {
let dropped_size =
std::mem::size_of::<TraceEvent>() + dropped.name.len() + dropped.category.len();
buffer.current_size = buffer.current_size.saturating_sub(dropped_size);
}
buffer.stats.events_dropped += 1;
buffer.stats.overruns += 1;
}
buffer.current_size += event_size;
buffer.stats.events_written += 1;
buffer.stats.peak_usage = buffer.stats.peak_usage.max(buffer.current_size);
buffer.events.push_back(event);
}
pub fn reset(&mut self) {
self.trace_sessions.clear();
let mut buffer = self
.trace_buffer
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
buffer.events.clear();
buffer.current_size = 0;
buffer.stats = BufferStats::default();
}
}
impl MemoryProfiler {
pub fn new(_config: &ProfilingConfig) -> Self {
Self {
tracking_sessions: HashMap::new(),
allocation_tracker: AllocationTracker::new(),
usage_snapshots: Vec::new(),
}
}
pub fn start_tracking(&mut self, session_id: &str) -> Result<()> {
let session = MemoryTrackingSession {
id: session_id.to_string(),
start_time: Instant::now(),
allocations: HashMap::new(),
stats: MemoryTrackingStats::default(),
};
self.tracking_sessions
.insert(session_id.to_string(), session);
Ok(())
}
pub fn record_allocation(
&mut self,
session_id: &str,
address: usize,
size: usize,
source: AllocationSource,
context: Option<String>,
) {
let session = self.session_mut(session_id);
session.allocations.insert(
address,
AllocationInfo {
address,
size,
timestamp: Instant::now(),
source,
stack_trace: None,
tags: Vec::new(),
},
);
session.stats.total_allocations += 1;
session.stats.current_allocations = session.allocations.len();
session.stats.current_memory_usage =
session.stats.current_memory_usage.saturating_add(size);
session.stats.peak_memory_usage = session
.stats
.peak_memory_usage
.max(session.stats.current_memory_usage);
self.allocation_tracker.record(AllocationEvent {
timestamp: Instant::now(),
event_type: AllocationEventType::Allocate,
address,
size,
context,
});
}
pub fn record_deallocation(
&mut self,
session_id: &str,
address: usize,
context: Option<String>,
) {
let session = self.session_mut(session_id);
let Some(info) = session.allocations.remove(&address) else {
return;
};
session.stats.total_deallocations += 1;
session.stats.current_allocations = session.allocations.len();
session.stats.current_memory_usage =
session.stats.current_memory_usage.saturating_sub(info.size);
self.allocation_tracker.record(AllocationEvent {
timestamp: Instant::now(),
event_type: AllocationEventType::Deallocate,
address,
size: info.size,
context,
});
}
pub fn capture_snapshot(&mut self, fragmentation: FragmentationInfo) {
let regions: Vec<MemoryRegion> = self
.allocation_tracker
.active_allocations
.values()
.map(|info| MemoryRegion {
start_address: info.address,
size: info.size,
region_type: MemoryRegionType::Data,
usage: RegionUsage {
used_bytes: info.size,
free_bytes: 0,
fragmentation: 0.0,
},
})
.collect();
let total_usage = regions.iter().map(|region| region.size).sum();
self.usage_snapshots.push(MemorySnapshot {
timestamp: Instant::now(),
regions,
total_usage,
fragmentation,
});
}
pub fn tracking_stats(&self, session_id: &str) -> Option<&MemoryTrackingStats> {
self.tracking_sessions
.get(session_id)
.map(|session| &session.stats)
}
pub fn recorded_events(&self) -> usize {
self.allocation_tracker.allocation_history.len()
}
pub fn usage_snapshots(&self) -> &[MemorySnapshot] {
&self.usage_snapshots
}
fn session_mut(&mut self, session_id: &str) -> &mut MemoryTrackingSession {
self.tracking_sessions
.entry(session_id.to_string())
.or_insert_with(|| MemoryTrackingSession {
id: session_id.to_string(),
start_time: Instant::now(),
allocations: HashMap::new(),
stats: MemoryTrackingStats::default(),
})
}
pub fn reset(&mut self) {
self.tracking_sessions.clear();
self.usage_snapshots.clear();
self.allocation_tracker.reset();
}
}
impl Default for AllocationTracker {
fn default() -> Self {
Self::new()
}
}
impl AllocationTracker {
pub fn new() -> Self {
Self {
active_allocations: HashMap::new(),
allocation_history: Vec::new(),
}
}
fn record(&mut self, event: AllocationEvent) {
match event.event_type {
AllocationEventType::Allocate | AllocationEventType::Reallocate => {
self.active_allocations.insert(
event.address,
AllocationInfo {
address: event.address,
size: event.size,
timestamp: event.timestamp,
source: AllocationSource::Runtime,
stack_trace: None,
tags: Vec::new(),
},
);
}
AllocationEventType::Deallocate => {
self.active_allocations.remove(&event.address);
}
}
self.allocation_history.push(event);
}
pub fn reset(&mut self) {
self.active_allocations.clear();
self.allocation_history.clear();
}
}
impl ProfileExportManager {
pub fn new(config: &ProfilingConfig) -> Self {
Self {
export_config: ExportConfig {
format: config.export_format.clone(),
output_dir: config.output_directory.clone(),
include_raw_data: config.detailed_mode,
compression: true,
include_metadata: true,
},
export_stats: ExportStatistics::default(),
}
}
pub fn export_counter_data(
&mut self,
counter_manager: &PerformanceCounterManager,
) -> Result<String> {
let export_start = Instant::now();
let filename = format!(
"{}/counters_{}_{}.json",
self.export_config.output_dir,
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map_err(|e| OptimError::from(e.to_string()))?
.as_secs(),
self.export_stats.files_exported
);
std::fs::create_dir_all(&self.export_config.output_dir)?;
let data = counter_manager
.counter_data
.read()
.unwrap_or_else(|poisoned| poisoned.into_inner());
let counters: Vec<serde_json::Value> = data
.values()
.map(|series| {
let samples: Vec<serde_json::Value> = series
.samples
.iter()
.map(|(timestamp, value)| {
serde_json::json!({
"age_us": timestamp.elapsed().as_micros() as u64,
"value": counter_value_to_json(value),
})
})
.collect();
serde_json::json!({
"name": series.counter_name,
"sample_count": series.statistics.sample_count,
"min": series.statistics.min,
"max": series.statistics.max,
"average": series.statistics.average,
"std_dev": series.statistics.std_dev,
"samples": samples,
})
})
.collect();
drop(data);
let payload = serde_json::json!({
"counters": counters,
"metadata": {
"available_counters": counter_manager.available_counters.len(),
"active_sessions": counter_manager.active_sessions.len(),
},
});
let mut file = File::create(&filename)?;
writeln!(
file,
"{}",
serde_json::to_string_pretty(&payload).map_err(|e| OptimError::from(e.to_string()))?
)?;
self.export_stats.files_exported += 1;
self.export_stats.export_time_us = export_start.elapsed().as_micros() as u64;
Ok(filename)
}
pub fn export_trace_data(&mut self, trace_collector: &TraceCollector) -> Result<String> {
let export_start = Instant::now();
let filename = format!(
"{}/trace_{}_{}.json",
self.export_config.output_dir,
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map_err(|e| OptimError::from(e.to_string()))?
.as_secs(),
self.export_stats.files_exported
);
std::fs::create_dir_all(&self.export_config.output_dir)?;
let buffer = trace_collector
.trace_buffer
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
let trace_events: Vec<serde_json::Value> = buffer
.events
.iter()
.map(|event| {
serde_json::json!({
"id": event.id,
"name": event.name,
"cat": event.category,
"ph": event_phase_code(&event.phase),
"age_us": event.timestamp.elapsed().as_micros() as u64,
"dur_us": event.duration.map(|d| d.as_micros() as u64),
"tid": event.thread_id,
"pid": event.process_id,
})
})
.collect();
let events_written = buffer.stats.events_written;
let events_dropped = buffer.stats.events_dropped;
drop(buffer);
let payload = serde_json::json!({
"traceEvents": trace_events,
"displayTimeUnit": "us",
"metadata": {
"events_written": events_written,
"events_dropped": events_dropped,
},
});
let mut file = File::create(&filename)?;
writeln!(
file,
"{}",
serde_json::to_string_pretty(&payload).map_err(|e| OptimError::from(e.to_string()))?
)?;
self.export_stats.files_exported += 1;
self.export_stats.export_time_us = export_start.elapsed().as_micros() as u64;
Ok(filename)
}
pub fn export_memory_data(&mut self, memory_profiler: &MemoryProfiler) -> Result<String> {
let export_start = Instant::now();
let filename = format!(
"{}/memory_{}_{}.json",
self.export_config.output_dir,
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map_err(|e| OptimError::from(e.to_string()))?
.as_secs(),
self.export_stats.files_exported
);
std::fs::create_dir_all(&self.export_config.output_dir)?;
let snapshots: Vec<serde_json::Value> = memory_profiler
.usage_snapshots
.iter()
.map(|snapshot| {
serde_json::json!({
"age_us": snapshot.timestamp.elapsed().as_micros() as u64,
"total_usage": snapshot.total_usage,
"region_count": snapshot.regions.len(),
"external_fragmentation": snapshot.fragmentation.external_fragmentation,
"internal_fragmentation": snapshot.fragmentation.internal_fragmentation,
})
})
.collect();
let allocations: Vec<serde_json::Value> = memory_profiler
.allocation_tracker
.allocation_history
.iter()
.map(|event| {
serde_json::json!({
"age_us": event.timestamp.elapsed().as_micros() as u64,
"event_type": allocation_event_type_str(&event.event_type),
"address": event.address,
"size": event.size,
"context": event.context,
})
})
.collect();
let payload = serde_json::json!({
"snapshots": snapshots,
"allocations": allocations,
});
let mut file = File::create(&filename)?;
writeln!(
file,
"{}",
serde_json::to_string_pretty(&payload).map_err(|e| OptimError::from(e.to_string()))?
)?;
self.export_stats.files_exported += 1;
self.export_stats.export_time_us = export_start.elapsed().as_micros() as u64;
Ok(filename)
}
}
fn counter_value_to_json(value: &CounterValue) -> serde_json::Value {
match value {
CounterValue::Integer(i) => serde_json::json!(i),
CounterValue::Float(f) => serde_json::json!(f),
CounterValue::Boolean(b) => serde_json::json!(b),
CounterValue::String(s) => serde_json::json!(s),
CounterValue::Histogram(bins) => serde_json::json!(bins
.iter()
.map(|(edge, count)| serde_json::json!([edge, count]))
.collect::<Vec<_>>()),
}
}
fn event_phase_code(phase: &EventPhase) -> &'static str {
match phase {
EventPhase::Begin => "B",
EventPhase::End => "E",
EventPhase::Instant => "I",
EventPhase::Complete => "X",
EventPhase::AsyncBegin => "b",
EventPhase::AsyncEnd => "e",
}
}
fn allocation_event_type_str(event_type: &AllocationEventType) -> &'static str {
match event_type {
AllocationEventType::Allocate => "allocate",
AllocationEventType::Deallocate => "deallocate",
AllocationEventType::Reallocate => "reallocate",
}
}
#[cfg(test)]
mod tests;