use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use std::thread;
pub struct Profiler {
sessions: Arc<Mutex<HashMap<String, ProfilingSession>>>,
global_stats: Arc<Mutex<GlobalProfilingStats>>,
}
impl Profiler {
pub fn new() -> Self {
Self {
sessions: Arc::new(Mutex::new(HashMap::new())),
global_stats: Arc::new(Mutex::new(GlobalProfilingStats::new())),
}
}
pub fn start_session(&self, session_id: &str, config: ProfilingConfig) -> ProfilingSession {
let session = ProfilingSession::new(session_id.to_string(), config);
{
let mut sessions = self.sessions.lock().unwrap();
sessions.insert(session_id.to_string(), session.clone());
}
session
}
pub fn end_session(&self, session_id: &str) -> Option<ProfilingResults> {
let mut sessions = self.sessions.lock().unwrap();
if let Some(mut session) = sessions.remove(session_id) {
session.end();
let results = session.get_results();
{
let mut global_stats = self.global_stats.lock().unwrap();
global_stats.update_from_session(&results);
}
Some(results)
} else {
None
}
}
pub fn get_active_sessions(&self) -> Vec<String> {
let sessions = self.sessions.lock().unwrap();
sessions.keys().cloned().collect()
}
pub fn get_global_stats(&self) -> GlobalProfilingStats {
self.global_stats.lock().unwrap().clone()
}
pub fn profile_function<T, F>(&self, name: &str, func: F) -> (T, FunctionProfile)
where
F: FnOnce() -> T,
{
let start_time = Instant::now();
let start_memory = get_current_memory_usage();
let result = func();
let end_time = Instant::now();
let end_memory = get_current_memory_usage();
let profile = FunctionProfile {
name: name.to_string(),
execution_time: end_time - start_time,
memory_allocated: end_memory.saturating_sub(start_memory),
cpu_cycles: estimate_cpu_cycles(end_time - start_time),
call_count: 1,
};
(result, profile)
}
pub async fn profile_async_function<T, F, Fut>(&self, name: &str, func: F) -> (T, FunctionProfile)
where
F: FnOnce() -> Fut,
Fut: std::future::Future<Output = T>,
{
let start_time = Instant::now();
let start_memory = get_current_memory_usage();
let result = func().await;
let end_time = Instant::now();
let end_memory = get_current_memory_usage();
let profile = FunctionProfile {
name: name.to_string(),
execution_time: end_time - start_time,
memory_allocated: end_memory.saturating_sub(start_memory),
cpu_cycles: estimate_cpu_cycles(end_time - start_time),
call_count: 1,
};
(result, profile)
}
pub fn generate_flame_graph(&self, session_id: &str) -> Option<FlameGraphData> {
let sessions = self.sessions.lock().unwrap();
if let Some(session) = sessions.get(session_id) {
Some(session.generate_flame_graph())
} else {
None
}
}
pub fn export_data(&self, session_id: &str, format: ExportFormat) -> Option<String> {
let sessions = self.sessions.lock().unwrap();
if let Some(session) = sessions.get(session_id) {
match format {
ExportFormat::Json => Some(session.export_json()),
ExportFormat::Csv => Some(session.export_csv()),
ExportFormat::FlameGraph => Some(session.export_flame_graph()),
}
} else {
None
}
}
}
#[derive(Debug, Clone)]
pub struct ProfilingSession {
id: String,
config: ProfilingConfig,
start_time: Instant,
end_time: Option<Instant>,
function_profiles: Arc<Mutex<Vec<FunctionProfile>>>,
memory_snapshots: Arc<Mutex<Vec<MemorySnapshot>>>,
call_stack: Arc<Mutex<Vec<StackFrame>>>,
}
impl ProfilingSession {
fn new(id: String, config: ProfilingConfig) -> Self {
Self {
id,
config,
start_time: Instant::now(),
end_time: None,
function_profiles: Arc::new(Mutex::new(Vec::new())),
memory_snapshots: Arc::new(Mutex::new(Vec::new())),
call_stack: Arc::new(Mutex::new(Vec::new())),
}
}
pub fn record_function(&self, profile: FunctionProfile) {
if self.config.profile_functions {
let mut profiles = self.function_profiles.lock().unwrap();
profiles.push(profile);
}
}
pub fn take_memory_snapshot(&self, label: &str) {
if self.config.profile_memory {
let snapshot = MemorySnapshot {
timestamp: Instant::now(),
label: label.to_string(),
memory_usage: get_current_memory_usage(),
heap_size: get_heap_size(),
allocated_objects: count_allocated_objects(),
};
let mut snapshots = self.memory_snapshots.lock().unwrap();
snapshots.push(snapshot);
}
}
pub fn push_call(&self, function_name: &str) {
if self.config.track_call_stack {
let frame = StackFrame {
function_name: function_name.to_string(),
entry_time: Instant::now(),
file: String::new(), line: 0,
};
let mut stack = self.call_stack.lock().unwrap();
stack.push(frame);
}
}
pub fn pop_call(&self) -> Option<Duration> {
if self.config.track_call_stack {
let mut stack = self.call_stack.lock().unwrap();
if let Some(frame) = stack.pop() {
return Some(Instant::now() - frame.entry_time);
}
}
None
}
fn end(&mut self) {
self.end_time = Some(Instant::now());
}
fn get_results(&self) -> ProfilingResults {
let total_duration = self.end_time.unwrap_or_else(Instant::now) - self.start_time;
let function_profiles = self.function_profiles.lock().unwrap().clone();
let memory_snapshots = self.memory_snapshots.lock().unwrap().clone();
ProfilingResults {
session_id: self.id.clone(),
total_duration,
function_profiles,
memory_snapshots,
call_stack_depth: self.call_stack.lock().unwrap().len(),
peak_memory_usage: memory_snapshots.iter()
.map(|s| s.memory_usage)
.max()
.unwrap_or(0),
}
}
fn generate_flame_graph(&self) -> FlameGraphData {
let function_profiles = self.function_profiles.lock().unwrap();
let mut flame_data = HashMap::new();
for profile in function_profiles.iter() {
let entry = flame_data.entry(profile.name.clone())
.or_insert(FlameGraphEntry {
name: profile.name.clone(),
self_time: Duration::default(),
total_time: Duration::default(),
call_count: 0,
children: HashMap::new(),
});
entry.self_time += profile.execution_time;
entry.total_time += profile.execution_time;
entry.call_count += profile.call_count;
}
FlameGraphData { entries: flame_data }
}
fn export_json(&self) -> String {
let results = self.get_results();
serde_json::to_string_pretty(&results).unwrap_or_else(|_| "{}".to_string())
}
fn export_csv(&self) -> String {
let function_profiles = self.function_profiles.lock().unwrap();
let mut csv = String::from("function_name,execution_time_ms,memory_allocated,call_count\n");
for profile in function_profiles.iter() {
csv.push_str(&format!(
"{},{},{},{}\n",
profile.name,
profile.execution_time.as_millis(),
profile.memory_allocated,
profile.call_count
));
}
csv
}
fn export_flame_graph(&self) -> String {
let flame_data = self.generate_flame_graph();
let mut output = String::new();
for (name, entry) in flame_data.entries {
output.push_str(&format!(
"{} {}\n",
name,
entry.self_time.as_millis()
));
}
output
}
}
#[derive(Debug, Clone)]
pub struct ProfilingConfig {
pub profile_functions: bool,
pub profile_memory: bool,
pub track_call_stack: bool,
pub sampling_interval: Duration,
pub max_stack_depth: usize,
}
impl Default for ProfilingConfig {
fn default() -> Self {
Self {
profile_functions: true,
profile_memory: true,
track_call_stack: true,
sampling_interval: Duration::from_millis(10),
max_stack_depth: 100,
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct FunctionProfile {
pub name: String,
pub execution_time: Duration,
pub memory_allocated: usize,
pub cpu_cycles: u64,
pub call_count: u64,
}
#[derive(Debug, Clone)]
pub struct MemorySnapshot {
pub timestamp: Instant,
pub label: String,
pub memory_usage: usize,
pub heap_size: usize,
pub allocated_objects: usize,
}
#[derive(Debug, Clone)]
pub struct StackFrame {
pub function_name: String,
pub entry_time: Instant,
pub file: String,
pub line: u32,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ProfilingResults {
pub session_id: String,
pub total_duration: Duration,
pub function_profiles: Vec<FunctionProfile>,
pub memory_snapshots: Vec<MemorySnapshot>,
pub call_stack_depth: usize,
pub peak_memory_usage: usize,
}
#[derive(Debug, Clone)]
pub struct GlobalProfilingStats {
pub total_sessions: usize,
pub total_execution_time: Duration,
pub average_session_duration: Duration,
pub most_expensive_functions: Vec<FunctionProfile>,
pub memory_usage_trend: Vec<usize>,
}
impl GlobalProfilingStats {
fn new() -> Self {
Self {
total_sessions: 0,
total_execution_time: Duration::default(),
average_session_duration: Duration::default(),
most_expensive_functions: Vec::new(),
memory_usage_trend: Vec::new(),
}
}
fn update_from_session(&mut self, results: &ProfilingResults) {
self.total_sessions += 1;
self.total_execution_time += results.total_duration;
self.average_session_duration = self.total_execution_time / self.total_sessions as u32;
for profile in &results.function_profiles {
self.update_expensive_functions(profile.clone());
}
self.memory_usage_trend.push(results.peak_memory_usage);
if self.memory_usage_trend.len() > 100 {
self.memory_usage_trend.remove(0);
}
}
fn update_expensive_functions(&mut self, profile: FunctionProfile) {
if let Some(existing) = self.most_expensive_functions.iter_mut()
.find(|p| p.name == profile.name) {
existing.execution_time += profile.execution_time;
existing.call_count += profile.call_count;
} else {
self.most_expensive_functions.push(profile);
}
self.most_expensive_functions.sort_by(|a, b| b.execution_time.cmp(&a.execution_time));
self.most_expensive_functions.truncate(20);
}
}
#[derive(Debug, Clone)]
pub struct FlameGraphData {
pub entries: HashMap<String, FlameGraphEntry>,
}
#[derive(Debug, Clone)]
pub struct FlameGraphEntry {
pub name: String,
pub self_time: Duration,
pub total_time: Duration,
pub call_count: u64,
pub children: HashMap<String, FlameGraphEntry>,
}
#[derive(Debug, Clone)]
pub enum ExportFormat {
Json,
Csv,
FlameGraph,
}
fn get_current_memory_usage() -> usize {
use memory_stats::memory_stats;
memory_stats().map(|s| s.physical_mem).unwrap_or(0)
}
fn get_heap_size() -> usize {
0
}
fn count_allocated_objects() -> usize {
0
}
fn estimate_cpu_cycles(duration: Duration) -> u64 {
(duration.as_nanos() as u64 * 3) / 1000
}
#[macro_export]
macro_rules! profile_function {
($profiler:expr, $name:expr, $block:block) => {{
let (result, profile) = $profiler.profile_function($name, || $block);
result
}};
}
#[macro_export]
macro_rules! profile_async_function {
($profiler:expr, $name:expr, $async_block:expr) => {{
let (result, profile) = $profiler.profile_async_function($name, || $async_block).await;
result
}};
}