Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ rusqlite = { version = "0.32", features = ["bundled"] }

[features]
hardware-wallet = ["dep:hidapi", "dep:trezor-client"]
memory-profiling = []

[dev-dependencies]
criterion = "0.5.1"
Expand Down
271 changes: 271 additions & 0 deletions src/commands/perf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::utils::{performance as perf, print as p};
use anyhow::Result;
use clap::Subcommand;
use std::collections::HashMap;
use std::collections::BTreeMap;

#[derive(Subcommand)]
pub enum PerfCommands {
Expand Down Expand Up @@ -143,6 +144,276 @@ pub async fn handle(cmd: PerfCommands) -> Result<()> {
}
}

// ── Advanced Profiling Commands ──────────────────────────────────────────────

#[derive(Subcommand)]
pub enum AdvancedPerfCommands {
/// Advanced performance analysis with bottleneck detection
Analyze {
/// Contract ID
contract: String,
/// Network (default: testnet)
#[arg(long, default_value = "testnet")]
network: String,
},
/// Detect performance regressions
DetectRegression {
/// Contract ID
contract: String,
/// Analysis period in hours (default: 24)
#[arg(long, default_value_t = 24)]
period_hours: u64,
/// Network (default: testnet)
#[arg(long, default_value = "testnet")]
network: String,
},
/// Compare performance across time periods
Compare {
/// Contract ID
contract: String,
/// Time window in hours (default: 24)
#[arg(long, default_value_t = 24)]
hours_back: u64,
/// Network (default: testnet)
#[arg(long, default_value = "testnet")]
network: String,
},
/// Generate comprehensive performance dashboard
GenerateDashboard {
/// Contract ID
contract: String,
/// Network (default: testnet)
#[arg(long, default_value = "testnet")]
network: String,
},
}

// ── Advanced Profiling Command Handlers ──────────────────────────────────────

pub async fn handle_advanced(cmd: AdvancedPerfCommands) -> Result<()> {
match cmd {
AdvancedPerfCommands::Analyze { contract, network } => analyze(contract, network),
AdvancedPerfCommands::DetectRegression { contract, period_hours, network } => detect_regression(contract, period_hours, network),
AdvancedPerfCommands::Compare { contract, hours_back, network } => compare(contract, hours_back, network),
AdvancedPerfCommands::GenerateDashboard { contract, network } => generate_dashboard(contract, network),
}
}

fn analyze(contract: String, network: String) -> Result<()> {
p::header("Advanced Performance Analysis");
p::separator();
p::kv("Contract", &contract);
p::kv("Network", &network);
p::separator();

let analysis = perf::analyze_bottlenecks(&contract)?;

println!();
p::info("Bottleneck Analysis Results");
p::kv("Overall Score", &format!("{:.1}/100", analysis.overall_score));
p::kv("Memory Leaks Detected", &analysis.memory_leaks_detected.to_string());

if !analysis.bottleneck_operations.is_empty() {
println!();
p::warn("Frequent Operations (Potential Bottlenecks):");
for op in &analysis.bottleneck_operations {
println!(" • {}", op);
}
}

if !analysis.high_gas_operations.is_empty() {
println!();
p::warn("High Gas Consumption Operations:");
for op in &analysis.high_gas_operations {
println!(" • {}", op);
}
}

if analysis.bottleneck_operations.is_empty() && analysis.high_gas_operations.is_empty() {
p::success("No significant bottlenecks detected!");
}

println!();
p::separator();
Ok(())
}

fn detect_regression(contract: String, period_hours: u64, network: String) -> Result<()> {
p::header("Performance Regression Detection");
p::separator();
p::kv("Contract", &contract);
p::kv("Network", &network);
p::kv("Analysis Period", &format!("{} hours", period_hours));
p::separator();

let report = perf::detect_regression(&contract, period_hours)?;

println!();
p::info("Regression Report");
p::kv("Baseline Avg Gas", &format!("{:.0}", report.baseline_avg));
p::kv("Current Avg Gas", &format!("{:.0}", report.current_avg));
p::kv("Change", &format!("{:+.1}%", report.regression_percentage));

println!();
p::info("Trends:");
for trend in &report.trends {
if trend.contains("increased") {
p::warn(&format!(" ⚠ {}", trend));
} else if trend.contains("decreased") {
p::success(&format!(" ✓ {}", trend));
} else {
p::info(&format!(" • {}", trend));
}
}

let regression_count = report.regression_points.iter().filter(|r| r.regression_detected).count();
if regression_count > 0 {
println!();
p::warn(&format!("{} regression points detected:", regression_count));
for point in &report.regression_points {
if point.regression_detected {
println!(
" {} gas={} time={}ms [{}]",
&point.timestamp[..19],
point.gas_used,
point.execution_time_ms,
if point.success { "OK" } else { "FAIL" }
);
}
}
} else {
p::success("No regressions detected!");
}

println!();
p::separator();
Ok(())
}

fn compare(contract: String, hours_back: u64, network: String) -> Result<()> {
p::header("Performance Comparison");
p::separator();
p::kv("Contract", &contract);
p::kv("Network", &network);
p::kv("Time Window", &format!("{} hours", hours_back));
p::separator();

let report = perf::compare_profiles(&contract, hours_back)?;

println!();
p::info("Comparison Results");

if !report.performance_differences.is_empty() {
for (metric, diff) in &report.performance_differences {
let label = metric.replace('_', " ");
if *diff > 0.0 {
p::warn(&format!(" {} +{:.1}% (regression)", label, diff));
} else {
p::success(&format!(" {} {:.1}% (improvement)", label, diff));
}
}
} else {
p::info("Insufficient data for comparison (need at least 2 snapshots)");
}

println!();
p::info("Recommendations:");
if report.recommendations.is_empty() {
p::success(" No specific recommendations at this time.");
} else {
for rec in &report.recommendations {
println!(" • {}", rec);
}
}

println!();
p::separator();
Ok(())
}

fn generate_dashboard(contract: String, network: String) -> Result<()> {
p::header("Performance Dashboard");
p::separator();
p::kv("Contract", &contract);
p::kv("Network", &network);
p::separator();

let dashboard = perf::generate_dashboard(&contract, &network)?;

println!();
p::info("═══ EXECUTION SUMMARY ═══");
p::kv("Total Executions", &dashboard.summary.total_executions.to_string());
p::kv("Avg Gas Used", &format!("{:.0}", dashboard.summary.avg_gas_used));
p::kv("Max Gas Used", &format!("{:.0}", dashboard.summary.max_gas_used));
p::kv("Avg Execution Time", &format!("{:.1}ms", dashboard.summary.avg_execution_time_ms));
p::kv("Success Rate", &format!("{:.1}%", dashboard.summary.success_rate));

println!();
p::info("═══ BOTTLENECK ANALYSIS ═══");
p::kv("Overall Score", &format!("{:.1}/100", dashboard.bottleneck_analysis.overall_score));
p::kv("Memory Leaks Detected", &dashboard.bottleneck_analysis.memory_leaks_detected.to_string());

if !dashboard.bottleneck_analysis.bottleneck_operations.is_empty() {
p::warn("Frequent Operations:");
for op in &dashboard.bottleneck_analysis.bottleneck_operations {
println!(" • {}", op);
}
}
if !dashboard.bottleneck_analysis.high_gas_operations.is_empty() {
p::warn("High Gas Operations:");
for op in &dashboard.bottleneck_analysis.high_gas_operations {
println!(" • {}", op);
}
}

println!();
p::info("═══ REGRESSION DETECTION ═══");
p::kv("Baseline Avg", &format!("{:.0}", dashboard.regression_report.baseline_avg));
p::kv("Current Avg", &format!("{:.0}", dashboard.regression_report.current_avg));
p::kv("Change", &format!("{:+.1}%", dashboard.regression_report.regression_percentage));
for trend in &dashboard.regression_report.trends {
if trend.contains("increased") {
p::warn(&format!(" ⚠ {}", trend));
} else if trend.contains("decreased") {
p::success(&format!(" ✓ {}", trend));
} else {
p::info(&format!(" • {}", trend));
}
}

println!();
p::info("═══ PERFORMANCE COMPARISON ═══");
if !dashboard.comparison_report.performance_differences.is_empty() {
for (metric, diff) in &dashboard.comparison_report.performance_differences {
let label = metric.replace('_', " ");
if *diff > 0.0 {
p::warn(&format!(" {} +{:.1}%", label, diff));
} else {
p::success(&format!(" {} {:.1}%", label, diff));
}
}
} else {
p::info(" No comparison data available");
}
for rec in &dashboard.comparison_report.recommendations {
println!(" • {}", rec);
}

if !dashboard.alerts.is_empty() {
println!();
p::warn("Configured Alerts:");
for alert in &dashboard.alerts {
println!(" • {} {} {} ({})", alert.metric_name,
if matches!(alert.direction, perf::AlertDirection::Above) { ">" } else { "<" },
alert.threshold, alert.message);
}
}

println!();
p::separator();
Ok(())
}

fn record(
contract: String,
operation: String,
Expand Down
6 changes: 6 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ enum Commands {
#[command(subcommand)]
Perf(commands::perf::PerfCommands),

/// Advanced contract performance analysis and profiling tools
#[command(subcommand)]
AdvancedPerf(commands::perf::AdvancedPerfCommands),

/// Contract documentation portal (generate, view, search)
#[command(subcommand)]
Docs(commands::docs::DocsCommands),
Expand Down Expand Up @@ -201,6 +205,7 @@ async fn main() {
Commands::Diagnostics(_) => "diagnostics",
Commands::TemplateVcs(_) => "template-vcs",
Commands::Perf(_) => "perf",
Commands::AdvancedPerf(_) => "advanced-perf",
Commands::Docs(_) => "docs",
Commands::Analytics(_) => "analytics",
Commands::External(_) => "external",
Expand Down Expand Up @@ -239,6 +244,7 @@ async fn main() {
Commands::Diagnostics(args) => commands::diagnostics::handle(args).await,
Commands::TemplateVcs(cmd) => commands::template_vcs::handle(cmd).await,
Commands::Perf(cmd) => commands::perf::handle(cmd).await,
Commands::AdvancedPerf(cmd) => commands::perf::handle_advanced(cmd).await,
Commands::Docs(cmd) => commands::docs::handle(cmd).await,
Commands::Analytics(cmd) => commands::analytics::handle(cmd).await,
Commands::External(args) => handle_external_plugin(args),
Expand Down
Loading