name: Performance Testing & Regression Detection on: push: branches: [ main, develop ] pull_request: branches: [ main, develop ] schedule: # Run performance tests daily at 2 AM UTC - cron: '0 2 * * *' env: CARGO_TERM_COLOR: always RUST_BACKTRACE: 1 jobs: performance-benchmarks: name: Performance Benchmarks runs-on: ubuntu-latest strategy: matrix: rust-version: [stable, beta, nightly] steps: - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 # Full history for performance comparison - name: Install Rust uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.rust-version }} components: rustfmt, clippy override: true - name: Cache cargo registry uses: actions/cache@v3 with: path: ~/.cargo/registry key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - name: Cache cargo index uses: actions/cache@v3 with: path: ~/.cargo/git key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} - name: Cache cargo build uses: actions/cache@v3 with: path: target key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} - name: Install system dependencies run: | sudo apt-get update sudo apt-get install -y build-essential pkg-config libssl-dev - name: Build performance audit tools run: | cd performance-audit cargo build --release --features benchmarks - name: Run performance benchmarks run: | cd performance-audit cargo bench --features benchmarks -- --output-format json > benchmark_results.json - name: Parse benchmark results run: | cd performance-audit # Create a simple benchmark parser cat > parse_benchmarks.py << 'EOF' import json import sys try: with open('benchmark_results.json', 'r') as f: data = json.load(f) # Extract benchmark results results = [] for benchmark in data.get('benchmarks', []): results.append({ 'name': benchmark.get('name', ''), 'mean': benchmark.get('mean', 0), 'std_dev': benchmark.get('std_dev', 0), 'iterations': benchmark.get('iterations', 0) }) # Save parsed results with open('parsed_benchmarks.json', 'w') as f: json.dump(results, f, indent=2) print(f"Parsed {len(results)} benchmark results") except Exception as e: print(f"Error parsing benchmarks: {e}") sys.exit(1) EOF python3 parse_benchmarks.py - name: Upload benchmark results uses: actions/upload-artifact@v3 with: name: benchmark-results-${{ matrix.rust-version }} path: performance-audit/parsed_benchmarks.json retention-days: 30 - name: Check performance thresholds run: | cd performance-audit # Create performance threshold checker cat > check_thresholds.py << 'EOF' import json import sys # Performance thresholds (in milliseconds) THRESHOLDS = { 'component_rendering': 16.0, # 60fps target 'memory_usage': 1000.0, # 1MB target 'bundle_size': 5.0, # 5KB target 'state_management': 0.1, # 100μs target 'accessibility': 0.05, # 50μs target 'theme_switching': 0.01, # 10μs target 'integration': 50.0, # 50ms target 'memory_leaks': 95.0, # 95% score target 'regression': 90.0, # 90% score target } try: with open('parsed_benchmarks.json', 'r') as f: results = json.load(f) failed_tests = [] for result in results: name = result['name'] mean = result['mean'] # Find matching threshold threshold = None for key, value in THRESHOLDS.items(): if key in name.lower(): threshold = value break if threshold is not None: if mean > threshold: failed_tests.append(f"{name}: {mean:.2f}ms > {threshold}ms threshold") if failed_tests: print("❌ Performance threshold violations:") for test in failed_tests: print(f" - {test}") sys.exit(1) else: print("✅ All performance thresholds met") except Exception as e: print(f"Error checking thresholds: {e}") sys.exit(1) EOF python3 check_thresholds.py memory-safety-tests: name: Memory Safety Tests runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Install Rust uses: actions-rs/toolchain@v1 with: toolchain: stable components: rustfmt, clippy override: true - name: Cache cargo dependencies uses: actions/cache@v3 with: path: | ~/.cargo/registry ~/.cargo/git target key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Install system dependencies run: | sudo apt-get update sudo apt-get install -y build-essential pkg-config libssl-dev valgrind - name: Build with memory safety features run: | cd performance-audit cargo build --release --features benchmarks - name: Run memory safety tests run: | cd performance-audit # Run memory safety tests with valgrind cargo test --release --features benchmarks memory_safety -- --nocapture - name: Run memory leak detection run: | cd performance-audit # Create memory leak detection script cat > memory_leak_test.sh << 'EOF' #!/bin/bash set -e echo "Running memory leak detection tests..." # Test component lifecycle echo "Testing component lifecycle..." cargo test --release memory_safety::tests::test_component_lifecycle_test -- --nocapture # Test event listener cleanup echo "Testing event listener cleanup..." cargo test --release memory_safety::tests::test_event_listener_cleanup_test -- --nocapture # Test signal cleanup echo "Testing signal cleanup..." cargo test --release memory_safety::tests::test_signal_cleanup_test -- --nocapture # Test context cleanup echo "Testing context cleanup..." cargo test --release memory_safety::tests::test_context_cleanup_test -- --nocapture # Test long-running stability echo "Testing long-running stability..." cargo test --release memory_safety::tests::test_long_running_stability_test -- --nocapture echo "✅ All memory safety tests passed" EOF chmod +x memory_leak_test.sh ./memory_leak_test.sh bundle-size-analysis: name: Bundle Size Analysis runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Install Rust uses: actions-rs/toolchain@v1 with: toolchain: stable override: true - name: Install wasm-pack run: | curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh - name: Cache cargo dependencies uses: actions/cache@v3 with: path: | ~/.cargo/registry ~/.cargo/git target key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Build WASM bundles run: | # Build example application to analyze bundle sizes cd examples/leptos wasm-pack build --target web --release --out-dir pkg - name: Analyze bundle sizes run: | cd examples/leptos/pkg # Create bundle size analyzer cat > analyze_bundle.py << 'EOF' import os import json def get_file_size(filepath): return os.path.getsize(filepath) def analyze_bundle(): results = {} # Analyze main bundle if os.path.exists('leptos_shadcn_ui_bg.wasm'): wasm_size = get_file_size('leptos_shadcn_ui_bg.wasm') results['wasm_bundle'] = wasm_size print(f"WASM bundle size: {wasm_size / 1024:.1f} KB") if os.path.exists('leptos_shadcn_ui.js'): js_size = get_file_size('leptos_shadcn_ui.js') results['js_bundle'] = js_size print(f"JS bundle size: {js_size / 1024:.1f} KB") # Check thresholds wasm_threshold = 500 * 1024 # 500KB js_threshold = 100 * 1024 # 100KB total_size = results.get('wasm_bundle', 0) + results.get('js_bundle', 0) print(f"Total bundle size: {total_size / 1024:.1f} KB") if total_size > wasm_threshold + js_threshold: print(f"❌ Bundle size exceeds threshold: {total_size / 1024:.1f} KB > {(wasm_threshold + js_threshold) / 1024:.1f} KB") return False else: print("✅ Bundle size within acceptable limits") return True if __name__ == "__main__": success = analyze_bundle() exit(0 if success else 1) EOF python3 analyze_bundle.py - name: Upload bundle analysis uses: actions/upload-artifact@v3 with: name: bundle-analysis path: examples/leptos/pkg/ retention-days: 30 performance-regression-detection: name: Performance Regression Detection runs-on: ubuntu-latest needs: [performance-benchmarks, memory-safety-tests, bundle-size-analysis] if: github.event_name == 'pull_request' steps: - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 - name: Download baseline benchmarks uses: actions/download-artifact@v3 with: name: benchmark-results-stable path: baseline-benchmarks/ - name: Download current benchmarks uses: actions/download-artifact@v3 with: name: benchmark-results-stable path: current-benchmarks/ - name: Compare performance run: | # Create performance comparison script cat > compare_performance.py << 'EOF' import json import sys def load_benchmarks(filepath): try: with open(filepath, 'r') as f: return json.load(f) except FileNotFoundError: return [] def compare_benchmarks(): baseline = load_benchmarks('baseline-benchmarks/parsed_benchmarks.json') current = load_benchmarks('current-benchmarks/parsed_benchmarks.json') if not baseline or not current: print("⚠️ No baseline or current benchmarks found") return True # Create lookup for current results current_lookup = {r['name']: r for r in current} regressions = [] improvements = [] for baseline_result in baseline: name = baseline_result['name'] baseline_mean = baseline_result['mean'] if name in current_lookup: current_mean = current_lookup[name]['mean'] change_percent = ((current_mean - baseline_mean) / baseline_mean) * 100 if change_percent > 5.0: # 5% regression threshold regressions.append({ 'name': name, 'baseline': baseline_mean, 'current': current_mean, 'change_percent': change_percent }) elif change_percent < -5.0: # 5% improvement threshold improvements.append({ 'name': name, 'baseline': baseline_mean, 'current': current_mean, 'change_percent': change_percent }) # Report results if regressions: print("❌ Performance regressions detected:") for reg in regressions: print(f" - {reg['name']}: {reg['change_percent']:.1f}% slower ({reg['baseline']:.2f}ms → {reg['current']:.2f}ms)") return False if improvements: print("✅ Performance improvements detected:") for imp in improvements: print(f" - {imp['name']}: {imp['change_percent']:.1f}% faster ({imp['baseline']:.2f}ms → {imp['current']:.2f}ms)") if not regressions and not improvements: print("✅ No significant performance changes detected") return True if __name__ == "__main__": success = compare_benchmarks() exit(0 if success else 1) EOF python3 compare_performance.py - name: Comment PR with performance results if: failure() uses: actions/github-script@v6 with: script: | github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: '🚨 **Performance Regression Detected**\n\nThis PR introduces performance regressions. Please review the performance test results and optimize the affected components before merging.' }) performance-report: name: Performance Report runs-on: ubuntu-latest needs: [performance-benchmarks, memory-safety-tests, bundle-size-analysis] if: always() steps: - name: Checkout code uses: actions/checkout@v4 - name: Download all artifacts uses: actions/download-artifact@v3 with: path: artifacts/ - name: Generate performance report run: | # Create performance report generator cat > generate_report.py << 'EOF' import json import os from datetime import datetime def generate_report(): report = { 'timestamp': datetime.now().isoformat(), 'commit': os.environ.get('GITHUB_SHA', 'unknown'), 'branch': os.environ.get('GITHUB_REF', 'unknown'), 'benchmarks': [], 'memory_tests': [], 'bundle_analysis': {} } # Process benchmark results for artifact_dir in os.listdir('artifacts/'): if artifact_dir.startswith('benchmark-results-'): benchmark_file = f'artifacts/{artifact_dir}/parsed_benchmarks.json' if os.path.exists(benchmark_file): with open(benchmark_file, 'r') as f: benchmarks = json.load(f) report['benchmarks'].extend(benchmarks) # Process bundle analysis bundle_dir = 'artifacts/bundle-analysis' if os.path.exists(bundle_dir): for file in os.listdir(bundle_dir): filepath = os.path.join(bundle_dir, file) if os.path.isfile(filepath): size = os.path.getsize(filepath) report['bundle_analysis'][file] = size # Save report with open('performance_report.json', 'w') as f: json.dump(report, f, indent=2) print("📊 Performance report generated") # Print summary print(f"Benchmarks: {len(report['benchmarks'])}") print(f"Bundle files: {len(report['bundle_analysis'])}") if __name__ == "__main__": generate_report() EOF python3 generate_report.py - name: Upload performance report uses: actions/upload-artifact@v3 with: name: performance-report path: performance_report.json retention-days: 90