use crate::core::performance::{
BenchmarkResult, BenchmarkSuite, LatencyMetrics, MemoryMetrics, MetricsTracker,
OptimizationAdvisor, OptimizationSuggestion, PerformanceProfiler as CoreProfiler,
ProfileResult, ThroughputMetrics,
};
use crate::error::Result;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use trustformers_core::errors::TrustformersError;
pub struct Profiler {
core_profiler: CoreProfiler,
advisor: OptimizationAdvisor,
benchmark_suite: BenchmarkSuite,
metrics_tracker: MetricsTracker,
config: ProfilerConfig,
session_start: Instant,
active_sessions: Arc<Mutex<HashMap<String, ProfileSession>>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProfilerConfig {
pub auto_enable: bool,
pub enable_memory: bool,
pub enable_advisor: bool,
pub enable_benchmarks: bool,
pub max_sessions: usize,
pub output_dir: Option<String>,
pub auto_save: bool,
}
impl Default for ProfilerConfig {
fn default() -> Self {
Self {
auto_enable: true,
enable_memory: true,
enable_advisor: true,
enable_benchmarks: false, max_sessions: 10,
output_dir: None,
auto_save: false,
}
}
}
#[derive(Debug, Clone)]
pub struct ProfileSession {
pub id: String,
pub name: String,
pub start_time: Instant,
pub end_time: Option<Instant>,
pub results: Option<ProfileResults>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProfileResults {
pub session_id: String,
pub total_duration: Duration,
pub operations: HashMap<String, ProfileResult>,
pub latency_metrics: LatencyMetrics,
pub throughput_metrics: ThroughputMetrics,
pub memory_metrics: Option<MemoryMetrics>,
pub optimization_suggestions: Vec<OptimizationSuggestion>,
pub benchmark_results: Option<Vec<BenchmarkResult>>,
pub summary: ProfileSummary,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProfileSummary {
pub total_operations: usize,
pub total_time: Duration,
pub avg_operation_time: Duration,
pub slowest_operation: String,
pub fastest_operation: String,
pub memory_efficiency: f64,
pub performance_score: f64,
pub bottlenecks_found: usize,
pub optimization_potential: f64,
}
impl Profiler {
pub fn new() -> Result<Self> {
Self::with_config(ProfilerConfig::default())
}
pub fn with_config(config: ProfilerConfig) -> Result<Self> {
let core_profiler = CoreProfiler::new();
if config.auto_enable {
core_profiler.enable();
}
let advisor = OptimizationAdvisor::new();
let benchmark_suite = BenchmarkSuite::new(trustformers_core::BenchmarkConfig::default());
let metrics_tracker = MetricsTracker::new(100);
Ok(Self {
core_profiler,
advisor,
benchmark_suite,
metrics_tracker,
config,
session_start: Instant::now(),
active_sessions: Arc::new(Mutex::new(HashMap::new())),
})
}
pub fn enable(&self) {
self.core_profiler.enable();
}
pub fn disable(&self) {
self.core_profiler.disable();
}
pub fn is_enabled(&self) -> bool {
self.core_profiler.is_enabled()
}
pub fn start_session(&self, name: &str) -> Result<String> {
let session_id = format!("{}_{}", name, chrono::Utc::now().timestamp());
let session = ProfileSession {
id: session_id.clone(),
name: name.to_string(),
start_time: Instant::now(),
end_time: None,
results: None,
};
let mut sessions = self.active_sessions.lock().map_err(|e| {
TrustformersError::runtime_error(format!("Failed to lock sessions: {}", e))
})?;
if sessions.len() >= self.config.max_sessions {
let oldest_id = sessions.values().min_by_key(|s| s.start_time).map(|s| s.id.clone());
if let Some(id) = oldest_id {
sessions.remove(&id);
}
}
sessions.insert(session_id.clone(), session);
Ok(session_id)
}
pub fn end_session(&self, session_id: &str) -> Result<ProfileResults> {
let mut sessions = self.active_sessions.lock().map_err(|e| {
TrustformersError::runtime_error(format!("Failed to lock sessions: {}", e))
})?;
let session = sessions.get_mut(session_id).ok_or_else(|| {
TrustformersError::invalid_input(format!("Session {} not found", session_id))
})?;
session.end_time = Some(Instant::now());
let total_duration = session.end_time.expect("end_time just set to Some on previous line")
- session.start_time;
let operations = self.core_profiler.get_results();
let latency_metrics = self.generate_latency_metrics(&operations);
let throughput_metrics = self.generate_throughput_metrics(&operations, total_duration);
let memory_metrics = if self.config.enable_memory {
Some(self.generate_memory_metrics())
} else {
None
};
let optimization_suggestions = if self.config.enable_advisor {
self.generate_optimization_suggestions(&operations)
} else {
Vec::new()
};
let benchmark_results =
if self.config.enable_benchmarks { Some(self.run_benchmarks()?) } else { None };
let summary = self.generate_summary(&operations, total_duration, &optimization_suggestions);
let results = ProfileResults {
session_id: session_id.to_string(),
total_duration,
operations,
latency_metrics,
throughput_metrics,
memory_metrics,
optimization_suggestions,
benchmark_results,
summary,
};
session.results = Some(results.clone());
if self.config.auto_save {
self.save_results(&results)?;
}
Ok(results)
}
pub fn profile_function<F, R>(&self, name: &str, f: F) -> Result<(R, ProfileResults)>
where
F: FnOnce() -> R,
{
let session_id = self.start_session(name)?;
let _guard = self.core_profiler.start_operation(name);
let result = f();
drop(_guard);
let profile_results = self.end_session(&session_id)?;
Ok((result, profile_results))
}
pub fn profile_function_lightweight<F, R>(&self, _name: &str, f: F) -> R
where
F: FnOnce() -> R,
{
f()
}
pub async fn profile_async<F, R>(&self, name: &str, f: F) -> Result<(R, ProfileResults)>
where
F: std::future::Future<Output = R>,
{
let session_id = self.start_session(name)?;
let _guard = self.core_profiler.start_operation(name);
let result = f.await;
drop(_guard);
let profile_results = self.end_session(&session_id)?;
Ok((result, profile_results))
}
pub fn get_active_sessions(&self) -> Result<Vec<ProfileSession>> {
let sessions = self.active_sessions.lock().map_err(|e| {
TrustformersError::runtime_error(format!("Failed to lock sessions: {}", e))
})?;
Ok(sessions.values().cloned().collect())
}
pub fn get_session_results(&self, session_id: &str) -> Result<Option<ProfileResults>> {
let sessions = self.active_sessions.lock().map_err(|e| {
TrustformersError::runtime_error(format!("Failed to lock sessions: {}", e))
})?;
Ok(sessions.get(session_id).and_then(|s| s.results.clone()))
}
pub fn clear(&self) -> Result<()> {
self.core_profiler.clear();
let mut sessions = self.active_sessions.lock().map_err(|e| {
TrustformersError::runtime_error(format!("Failed to lock sessions: {}", e))
})?;
sessions.clear();
Ok(())
}
pub fn get_dashboard_url(&self) -> Option<String> {
None
}
pub fn export_results(&self, session_id: &str, format: ExportFormat, path: &str) -> Result<()> {
let sessions = self.active_sessions.lock().map_err(|e| {
TrustformersError::runtime_error(format!("Failed to lock sessions: {}", e))
})?;
let session = sessions.get(session_id).ok_or_else(|| {
TrustformersError::invalid_input(format!("Session {} not found", session_id))
})?;
let results = session.results.as_ref().ok_or_else(|| {
TrustformersError::invalid_input("Session has no results".to_string())
})?;
match format {
ExportFormat::Json => {
let json = serde_json::to_string_pretty(results).map_err(|e| {
TrustformersError::serialization_error(format!(
"JSON serialization failed: {}",
e
))
})?;
std::fs::write(path, json).map_err(|e| {
TrustformersError::io_error(format!("File write failed: {}", e))
})?;
},
ExportFormat::Html => {
let html = self.generate_html_report(results);
std::fs::write(path, html).map_err(|e| {
TrustformersError::io_error(format!("File write failed: {}", e))
})?;
},
ExportFormat::Flamegraph => {
self.core_profiler.export_flamegraph(path).map_err(|e| {
TrustformersError::invalid_operation(format!("Flamegraph export failed: {}", e))
})?;
},
ExportFormat::Csv => {
let csv = self.generate_csv_report(results);
std::fs::write(path, csv).map_err(|e| {
TrustformersError::io_error(format!("File write failed: {}", e))
})?;
},
}
Ok(())
}
fn generate_latency_metrics(
&self,
operations: &HashMap<String, ProfileResult>,
) -> LatencyMetrics {
let mut total_time = Duration::ZERO;
let mut operation_count = 0;
let mut min_time = Duration::MAX;
let mut max_time = Duration::ZERO;
for result in operations.values() {
total_time += result.total_time;
operation_count += result.call_count;
min_time = min_time.min(result.min_time);
max_time = max_time.max(result.max_time);
}
let avg_time = if operation_count > 0 {
total_time / operation_count as u32
} else {
Duration::ZERO
};
LatencyMetrics {
count: operations.len(),
mean_ms: avg_time.as_millis() as f64,
median_ms: avg_time.as_millis() as f64, std_dev_ms: 0.0, min_ms: min_time.as_millis() as f64,
max_ms: max_time.as_millis() as f64,
p50_ms: avg_time.as_millis() as f64, p90_ms: max_time.as_millis() as f64, p95_ms: max_time.as_millis() as f64,
p99_ms: max_time.as_millis() as f64,
p999_ms: max_time.as_millis() as f64,
window_duration: Duration::from_secs(3600), }
}
fn generate_throughput_metrics(
&self,
operations: &HashMap<String, ProfileResult>,
total_duration: Duration,
) -> ThroughputMetrics {
let total_operations: usize = operations.values().map(|r| r.call_count).sum();
let operations_per_second = if total_duration.as_secs() > 0 {
total_operations as f64 / total_duration.as_secs_f64()
} else {
0.0
};
ThroughputMetrics {
tokens_per_second: operations_per_second * 100.0, batches_per_second: operations_per_second / 10.0, samples_per_second: operations_per_second,
avg_batch_size: 10.0, avg_sequence_length: 100.0, total_tokens: total_operations * 100, total_batches: total_operations / 10, total_duration,
}
}
fn generate_memory_metrics(&self) -> MemoryMetrics {
MemoryMetrics {
current_bytes: 1024 * 1024 * 80, peak_bytes: 1024 * 1024 * 100, allocated_bytes: 1024 * 1024 * 120, reserved_bytes: 1024 * 1024 * 150, num_allocations: 1000, num_deallocations: 950, fragmentation_percent: 5.0, }
}
fn generate_optimization_suggestions(
&self,
operations: &HashMap<String, ProfileResult>,
) -> Vec<OptimizationSuggestion> {
let mut suggestions = Vec::new();
let mut sorted_ops: Vec<_> = operations.iter().collect();
sorted_ops.sort_by_key(|(_, item)| std::cmp::Reverse(item.total_time));
if let Some((name, result)) = sorted_ops.first() {
if result.total_time > Duration::from_millis(100) {
suggestions.push(OptimizationSuggestion {
id: "slow_operation".to_string(),
category: crate::core::performance::OptimizationCategory::Compute,
impact: crate::core::performance::ImpactLevel::High,
difficulty: crate::core::performance::Difficulty::Medium,
title: format!("Optimize slow operation: {}", name),
description: format!("Operation {} is taking {:.2}ms, consider optimization", name, result.total_time.as_secs_f64() * 1000.0),
implementation_steps: vec![
"Profile the operation in detail".to_string(),
"Consider algorithmic improvements".to_string(),
"Enable hardware acceleration".to_string(),
],
expected_improvement: crate::core::performance::PerformanceImprovement {
latency_reduction: Some(30.0),
throughput_increase: Some(25.0),
memory_reduction: Some(10.0),
other_metrics: std::collections::HashMap::new(),
},
code_examples: Some(vec![crate::core::performance::CodeExample {
language: "rust".to_string(),
code: format!("// Optimize {} operation\n// Consider using GPU acceleration or kernel fusion", name),
description: "Example optimization approach".to_string(),
}]),
warnings: vec![],
related_suggestions: vec![],
});
}
}
suggestions
}
fn generate_summary(
&self,
operations: &HashMap<String, ProfileResult>,
total_duration: Duration,
suggestions: &[OptimizationSuggestion],
) -> ProfileSummary {
let total_operations = operations.len();
let total_time: Duration = operations.values().map(|r| r.total_time).sum();
let avg_operation_time = if total_operations > 0 {
total_time / total_operations as u32
} else {
Duration::ZERO
};
let slowest_operation = operations
.iter()
.max_by_key(|(_, r)| r.total_time)
.map(|(name, _)| name.clone())
.unwrap_or_else(|| "none".to_string());
let fastest_operation = operations
.iter()
.min_by_key(|(_, r)| r.total_time)
.map(|(name, _)| name.clone())
.unwrap_or_else(|| "none".to_string());
let memory_efficiency = 85.0; let performance_score = if total_time.as_millis() < 100 { 90.0 } else { 60.0 };
let bottlenecks_found = suggestions.len();
let optimization_potential = suggestions.len() as f64 * 10.0;
ProfileSummary {
total_operations,
total_time,
avg_operation_time,
slowest_operation,
fastest_operation,
memory_efficiency,
performance_score,
bottlenecks_found,
optimization_potential,
}
}
fn run_benchmarks(&self) -> Result<Vec<BenchmarkResult>> {
Ok(vec![])
}
fn save_results(&self, results: &ProfileResults) -> Result<()> {
if let Some(output_dir) = &self.config.output_dir {
let filename = format!("{}/profile_{}.json", output_dir, results.session_id);
let json = serde_json::to_string_pretty(results).map_err(|e| {
TrustformersError::serialization_error(format!("JSON serialization failed: {}", e))
})?;
std::fs::write(&filename, json)
.map_err(|e| TrustformersError::io_error(format!("File write failed: {}", e)))?;
}
Ok(())
}
fn generate_html_report(&self, results: &ProfileResults) -> String {
format!(
r#"<!DOCTYPE html>
<html>
<head>
<title>TrustformeRS Performance Report</title>
<style>
body {{ font-family: Arial, sans-serif; margin: 20px; }}
.header {{ background: #f0f0f0; padding: 20px; border-radius: 5px; }}
.section {{ margin: 20px 0; }}
.metric {{ display: inline-block; margin: 10px; padding: 10px; background: #e9e9e9; border-radius: 3px; }}
table {{ border-collapse: collapse; width: 100%; }}
th, td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
th {{ background-color: #f2f2f2; }}
</style>
</head>
<body>
<div class="header">
<h1>TrustformeRS Performance Report</h1>
<p>Session: {}</p>
<p>Duration: {:.2}ms</p>
</div>
<div class="section">
<h2>Summary</h2>
<div class="metric">Operations: {}</div>
<div class="metric">Performance Score: {:.1}</div>
<div class="metric">Memory Efficiency: {:.1}%</div>
<div class="metric">Bottlenecks: {}</div>
</div>
<div class="section">
<h2>Operations</h2>
<table>
<tr><th>Operation</th><th>Calls</th><th>Total Time (ms)</th><th>Avg Time (ms)</th></tr>
{}
</table>
</div>
<div class="section">
<h2>Optimization Suggestions</h2>
<ul>
{}
</ul>
</div>
</body>
</html>"#,
results.session_id,
results.total_duration.as_secs_f64() * 1000.0,
results.summary.total_operations,
results.summary.performance_score,
results.summary.memory_efficiency,
results.summary.bottlenecks_found,
results
.operations
.iter()
.map(|(name, result)| format!(
"<tr><td>{}</td><td>{}</td><td>{:.2}</td><td>{:.2}</td></tr>",
name,
result.call_count,
result.total_time.as_secs_f64() * 1000.0,
result.avg_time.as_secs_f64() * 1000.0
))
.collect::<Vec<_>>()
.join("\n"),
results
.optimization_suggestions
.iter()
.map(|s| format!("<li>{}: {}</li>", s.title, s.description))
.collect::<Vec<_>>()
.join("\n")
)
}
fn generate_csv_report(&self, results: &ProfileResults) -> String {
let mut csv = String::from(
"Operation,Calls,Total Time (ms),Avg Time (ms),Min Time (ms),Max Time (ms)\n",
);
for (name, result) in &results.operations {
csv.push_str(&format!(
"{},{},{:.2},{:.2},{:.2},{:.2}\n",
name,
result.call_count,
result.total_time.as_secs_f64() * 1000.0,
result.avg_time.as_secs_f64() * 1000.0,
result.min_time.as_secs_f64() * 1000.0,
result.max_time.as_secs_f64() * 1000.0
));
}
csv
}
}
impl Default for Profiler {
fn default() -> Self {
Self::new().expect("Profiler::new should not fail with default config")
}
}
#[derive(Debug, Clone, Copy)]
pub enum ExportFormat {
Json,
Html,
Flamegraph,
Csv,
}
static GLOBAL_PROFILER: std::sync::OnceLock<Profiler> = std::sync::OnceLock::new();
pub type GlobalProfiler = Profiler;
pub fn get_global_profiler() -> &'static Profiler {
GLOBAL_PROFILER
.get_or_init(|| Profiler::new().expect("Profiler::new should not fail for global instance"))
}
#[macro_export]
macro_rules! profile_operation {
($name:expr, $code:block) => {{
$crate::profiler::get_global_profiler().profile_function($name, || $code)
}};
}
pub fn profile_fn<F, R>(name: &str, f: F) -> Result<(R, ProfileResults)>
where
F: FnOnce() -> R,
{
get_global_profiler().profile_function(name, f)
}
pub async fn profile_async<F, R>(name: &str, f: F) -> Result<(R, ProfileResults)>
where
F: std::future::Future<Output = R>,
{
get_global_profiler().profile_async(name, f).await
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread::sleep;
#[test]
fn test_profiler_creation() {
let profiler = Profiler::new();
assert!(profiler.is_ok());
let profiler = profiler.expect("operation failed in test");
assert!(profiler.is_enabled()); }
#[test]
fn test_session_management() {
let profiler = Profiler::new().expect("operation failed in test");
let session_id = profiler.start_session("test_session").expect("operation failed in test");
assert!(!session_id.is_empty());
sleep(Duration::from_millis(10));
let results = profiler.end_session(&session_id).expect("operation failed in test");
assert_eq!(results.session_id, session_id);
assert!(results.total_duration > Duration::ZERO);
}
#[test]
fn test_profile_function() {
let profiler = Profiler::new().expect("operation failed in test");
let (result, profile_results) = profiler
.profile_function("test_operation", || {
sleep(Duration::from_millis(10));
42
})
.expect("operation failed in test");
assert_eq!(result, 42);
assert!(!profile_results.operations.is_empty());
assert!(profile_results.total_duration > Duration::ZERO);
}
#[test]
fn test_export_formats() {
let profiler = Profiler::new().expect("operation failed in test");
let (_, results) = profiler
.profile_function("export_test", || {
sleep(Duration::from_millis(5));
})
.expect("operation failed in test");
let html = profiler.generate_html_report(&results);
assert!(html.contains("TrustformeRS Performance Report"));
let csv = profiler.generate_csv_report(&results);
assert!(csv.contains("Operation,Calls"));
}
#[test]
fn test_global_profiler() {
let (result, profile_results) = profile_fn("global_test", || {
sleep(Duration::from_millis(5));
"test"
})
.expect("operation failed in test");
assert_eq!(result, "test");
assert!(!profile_results.operations.is_empty());
}
}