Performance Tests #28
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Performance Tests | |
| on: | |
| pull_request: | |
| branches: [main, develop] | |
| push: | |
| branches: [main] | |
| schedule: | |
| # Run nightly at 2 AM UTC | |
| - cron: '0 2 * * *' | |
| workflow_dispatch: | |
| env: | |
| CARGO_TERM_COLOR: always | |
| jobs: | |
| backend-performance: | |
| name: Backend Performance Tests | |
| runs-on: ubuntu-latest | |
| services: | |
| postgres: | |
| image: postgres:15 | |
| env: | |
| POSTGRES_PASSWORD: postgres | |
| POSTGRES_DB: predictiq_test | |
| options: >- | |
| --health-cmd pg_isready | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| ports: | |
| - 5432:5432 | |
| redis: | |
| image: redis:7-alpine | |
| options: >- | |
| --health-cmd "redis-cli ping" | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| ports: | |
| - 6379:6379 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Rust | |
| uses: actions-rs/toolchain@v1 | |
| with: | |
| toolchain: stable | |
| override: true | |
| - name: Cache Rust dependencies | |
| uses: actions/cache@v3 | |
| with: | |
| path: | | |
| ~/.cargo/bin/ | |
| ~/.cargo/registry/index/ | |
| ~/.cargo/registry/cache/ | |
| ~/.cargo/git/db/ | |
| target/ | |
| key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} | |
| - name: Build API service | |
| working-directory: services/api | |
| run: cargo build --release | |
| - name: Run database migrations | |
| working-directory: services/api | |
| env: | |
| DATABASE_URL: postgresql://postgres:postgres@localhost:5432/predictiq_test | |
| run: | | |
| cargo install sqlx-cli --no-default-features --features postgres | |
| sqlx migrate run | |
| - name: Start API server | |
| working-directory: services/api | |
| env: | |
| DATABASE_URL: postgresql://postgres:postgres@localhost:5432/predictiq_test | |
| REDIS_URL: redis://localhost:6379 | |
| RUST_LOG: info | |
| run: | | |
| cargo run --release & | |
| echo $! > api.pid | |
| sleep 10 | |
| - name: Wait for API to be ready | |
| run: | | |
| timeout 30 bash -c 'until curl -f http://localhost:8080/health; do sleep 1; done' | |
| - name: Install k6 | |
| run: | | |
| sudo gpg -k | |
| sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69 | |
| echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list | |
| sudo apt-get update | |
| sudo apt-get install k6 | |
| - name: Run smoke tests | |
| working-directory: performance | |
| env: | |
| API_URL: http://localhost:8080 | |
| run: k6 run --out json=backend/reports/smoke-test-raw.json backend/k6/smoke-test.js | |
| - name: Run load tests | |
| if: github.event_name != 'pull_request' | |
| working-directory: performance | |
| env: | |
| API_URL: http://localhost:8080 | |
| run: k6 run --out json=backend/reports/load-test-raw.json backend/k6/load-test.js | |
| - name: Run cache tests | |
| working-directory: performance | |
| env: | |
| API_URL: http://localhost:8080 | |
| run: k6 run --out json=backend/reports/cache-test-raw.json backend/k6/cache-test.js | |
| - name: Run rate limit tests | |
| working-directory: performance | |
| env: | |
| API_URL: http://localhost:8080 | |
| run: k6 run --out json=backend/reports/rate-limit-test-raw.json backend/k6/rate-limit-test.js | |
| - name: Run stress tests (nightly only) | |
| if: github.event_name == 'schedule' | |
| working-directory: performance | |
| env: | |
| API_URL: http://localhost:8080 | |
| run: k6 run --out json=backend/reports/stress-test-raw.json backend/k6/stress-test.js | |
| - name: Generate performance report | |
| if: always() | |
| working-directory: performance | |
| run: | | |
| npm install | |
| node scripts/generate-report.js | |
| - name: Upload performance reports | |
| if: always() | |
| uses: actions/upload-artifact@v3 | |
| with: | |
| name: performance-reports | |
| path: performance/backend/reports/ | |
| retention-days: 30 | |
| - name: Check performance thresholds | |
| if: always() | |
| working-directory: performance | |
| run: | | |
| if [ -f backend/reports/load-test-summary.json ]; then | |
| node -e " | |
| const data = require('./backend/reports/load-test-summary.json'); | |
| const p95 = data.metrics.http_req_duration.values['p(95)']; | |
| const errorRate = data.metrics.http_req_failed.values.rate; | |
| console.log('Performance Metrics:'); | |
| console.log('P95 Response Time:', p95.toFixed(2), 'ms'); | |
| console.log('Error Rate:', (errorRate * 100).toFixed(2), '%'); | |
| if (p95 > 200) { | |
| console.error('❌ P95 response time exceeds 200ms threshold'); | |
| process.exit(1); | |
| } | |
| if (errorRate > 0.001) { | |
| console.error('❌ Error rate exceeds 0.1% threshold'); | |
| process.exit(1); | |
| } | |
| console.log('✅ All performance thresholds met'); | |
| " | |
| fi | |
| - name: Comment PR with results | |
| if: github.event_name == 'pull_request' && always() | |
| uses: actions/github-script@v6 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const path = 'performance/backend/reports/smoke-test-summary.json'; | |
| if (fs.existsSync(path)) { | |
| const data = JSON.parse(fs.readFileSync(path, 'utf8')); | |
| const metrics = data.metrics; | |
| const comment = `## 🚀 Performance Test Results | |
| **Smoke Test Summary:** | |
| - Total Requests: ${metrics.http_reqs.values.count} | |
| - Avg Response Time: ${metrics.http_req_duration.values.avg.toFixed(2)}ms | |
| - P95 Response Time: ${metrics.http_req_duration.values['p(95)'].toFixed(2)}ms | |
| - Error Rate: ${(metrics.http_req_failed.values.rate * 100).toFixed(2)}% | |
| **Status:** ${metrics.http_req_duration.values['p(95)'] < 200 && metrics.http_req_failed.values.rate < 0.001 ? '✅ PASS' : '❌ FAIL'} | |
| [View detailed report](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) | |
| `; | |
| github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: comment | |
| }); | |
| } | |
| - name: Stop API server | |
| if: always() | |
| run: | | |
| if [ -f services/api/api.pid ]; then | |
| kill $(cat services/api/api.pid) || true | |
| fi | |
| contract-benchmarks: | |
| name: Smart Contract Benchmarks | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Rust | |
| uses: actions-rs/toolchain@v1 | |
| with: | |
| toolchain: stable | |
| override: true | |
| - name: Cache Rust dependencies | |
| uses: actions/cache@v3 | |
| with: | |
| path: | | |
| ~/.cargo/bin/ | |
| ~/.cargo/registry/index/ | |
| ~/.cargo/registry/cache/ | |
| ~/.cargo/git/db/ | |
| target/ | |
| key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} | |
| - name: Install Soroban CLI | |
| run: cargo install --locked soroban-cli --version 20.0.0 | |
| - name: Run contract benchmarks | |
| working-directory: contracts/predict-iq | |
| run: | | |
| cargo bench --bench gas_benchmark -- --save-baseline main | |
| - name: Upload benchmark results | |
| uses: actions/upload-artifact@v3 | |
| with: | |
| name: contract-benchmarks | |
| path: contracts/predict-iq/target/criterion/ | |
| retention-days: 30 | |
| - name: Compare with baseline | |
| if: github.event_name == 'pull_request' | |
| working-directory: contracts/predict-iq | |
| run: | | |
| if [ -d "target/criterion/baseline" ]; then | |
| cargo bench --bench gas_benchmark -- --baseline main | |
| fi | |
| performance-regression: | |
| name: Performance Regression Detection | |
| runs-on: ubuntu-latest | |
| needs: [backend-performance] | |
| if: github.event_name == 'pull_request' | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Download current results | |
| uses: actions/download-artifact@v3 | |
| with: | |
| name: performance-reports | |
| path: current-results/ | |
| - name: Download baseline results | |
| continue-on-error: true | |
| uses: dawidd6/action-download-artifact@v2 | |
| with: | |
| workflow: performance.yml | |
| branch: main | |
| name: performance-reports | |
| path: baseline-results/ | |
| - name: Compare results | |
| run: | | |
| if [ -f baseline-results/load-test-summary.json ] && [ -f current-results/load-test-summary.json ]; then | |
| node -e " | |
| const baseline = require('./baseline-results/load-test-summary.json'); | |
| const current = require('./current-results/load-test-summary.json'); | |
| const baselineP95 = baseline.metrics.http_req_duration.values['p(95)']; | |
| const currentP95 = current.metrics.http_req_duration.values['p(95)']; | |
| const regression = ((currentP95 - baselineP95) / baselineP95) * 100; | |
| console.log('Performance Comparison:'); | |
| console.log('Baseline P95:', baselineP95.toFixed(2), 'ms'); | |
| console.log('Current P95:', currentP95.toFixed(2), 'ms'); | |
| console.log('Change:', regression.toFixed(2), '%'); | |
| if (regression > 10) { | |
| console.error('⚠️ Performance regression detected: +' + regression.toFixed(2) + '%'); | |
| process.exit(1); | |
| } | |
| console.log('✅ No significant performance regression'); | |
| " | |
| else | |
| echo "⚠️ Baseline results not found, skipping comparison" | |
| fi |