← back to Handbag Auth Nextjs

auction-viewer/benchmark-api.js

289 lines

#!/usr/bin/env node
/**
 * API Performance Benchmark Tool
 * Tests API response times and throughput
 */

const axios = require('axios');
const fs = require('fs');
const path = require('path');

const BASE_URL = process.env.BASE_URL || 'http://45.61.58.125:7500';
const CONCURRENT_REQUESTS = 10;
const WARMUP_REQUESTS = 5;

class BenchmarkResults {
  constructor() {
    this.results = [];
  }

  add(endpoint, duration, statusCode, cached = false) {
    this.results.push({
      endpoint,
      duration,
      statusCode,
      cached,
      timestamp: Date.now()
    });
  }

  report() {
    const grouped = {};

    this.results.forEach(r => {
      if (!grouped[r.endpoint]) {
        grouped[r.endpoint] = [];
      }
      grouped[r.endpoint].push(r.duration);
    });

    const report = {
      summary: {},
      details: []
    };

    for (const [endpoint, durations] of Object.entries(grouped)) {
      const sorted = durations.sort((a, b) => a - b);
      const avg = sorted.reduce((a, b) => a + b, 0) / sorted.length;
      const min = sorted[0];
      const max = sorted[sorted.length - 1];
      const p50 = sorted[Math.floor(sorted.length * 0.5)];
      const p95 = sorted[Math.floor(sorted.length * 0.95)];
      const p99 = sorted[Math.floor(sorted.length * 0.99)];

      report.details.push({
        endpoint,
        requests: sorted.length,
        avg: Math.round(avg),
        min: Math.round(min),
        max: Math.round(max),
        p50: Math.round(p50),
        p95: Math.round(p95),
        p99: Math.round(p99)
      });
    }

    // Overall summary
    const allDurations = this.results.map(r => r.duration);
    const totalRequests = allDurations.length;
    const avgDuration = allDurations.reduce((a, b) => a + b, 0) / totalRequests;
    const successCount = this.results.filter(r => r.statusCode === 200).length;

    report.summary = {
      totalRequests,
      successRate: ((successCount / totalRequests) * 100).toFixed(2) + '%',
      avgResponseTime: Math.round(avgDuration) + 'ms',
      throughput: (totalRequests / (this.getTotalDuration() / 1000)).toFixed(2) + ' req/s'
    };

    return report;
  }

  getTotalDuration() {
    if (this.results.length === 0) return 0;
    const first = Math.min(...this.results.map(r => r.timestamp));
    const last = Math.max(...this.results.map(r => r.timestamp));
    return last - first;
  }
}

const benchmarks = new BenchmarkResults();

async function benchmark(name, url, options = {}) {
  console.log(`\n🔄 Benchmarking: ${name}`);
  console.log(`   URL: ${url}`);

  // Warmup
  console.log(`   Warming up (${WARMUP_REQUESTS} requests)...`);
  for (let i = 0; i < WARMUP_REQUESTS; i++) {
    try {
      await axios.get(url, { timeout: 30000 });
    } catch (error) {
      console.log(`   ⚠️  Warmup request failed: ${error.message}`);
    }
  }

  // Actual benchmark
  const requests = options.requests || CONCURRENT_REQUESTS;
  console.log(`   Running ${requests} concurrent requests...`);

  const startTime = Date.now();
  const promises = [];

  for (let i = 0; i < requests; i++) {
    promises.push(
      (async () => {
        const reqStart = Date.now();
        try {
          const response = await axios.get(url, { timeout: 30000 });
          const duration = Date.now() - reqStart;
          const cached = response.headers['x-cache'] === 'HIT' || duration < 10;

          benchmarks.add(name, duration, response.status, cached);

          return {
            success: true,
            duration,
            status: response.status,
            cached
          };
        } catch (error) {
          const duration = Date.now() - reqStart;
          benchmarks.add(name, duration, error.response?.status || 0);

          return {
            success: false,
            duration,
            error: error.message
          };
        }
      })()
    );
  }

  const results = await Promise.all(promises);
  const endTime = Date.now();
  const totalDuration = endTime - startTime;

  // Calculate stats
  const successful = results.filter(r => r.success).length;
  const durations = results.filter(r => r.success).map(r => r.duration);
  const avgDuration = durations.reduce((a, b) => a + b, 0) / durations.length;
  const minDuration = Math.min(...durations);
  const maxDuration = Math.max(...durations);
  const throughput = (requests / (totalDuration / 1000)).toFixed(2);

  console.log(`   ✅ Success: ${successful}/${requests}`);
  console.log(`   ⏱️  Avg: ${Math.round(avgDuration)}ms | Min: ${minDuration}ms | Max: ${maxDuration}ms`);
  console.log(`   🚀 Throughput: ${throughput} req/s`);

  return results;
}

async function runBenchmarks() {
  console.log('═══════════════════════════════════════════════════════');
  console.log('    LUXVAULT API PERFORMANCE BENCHMARK');
  console.log('═══════════════════════════════════════════════════════');
  console.log(`Base URL: ${BASE_URL}`);
  console.log(`Concurrent Requests: ${CONCURRENT_REQUESTS}`);
  console.log(`Warmup Requests: ${WARMUP_REQUESTS}`);
  console.log('═══════════════════════════════════════════════════════');

  try {
    // Health check
    console.log('\n📊 Testing health endpoint...');
    await benchmark('Health Check', `${BASE_URL}/health`, { requests: 20 });

    // Basic endpoints
    await benchmark('Get All Auctions', `${BASE_URL}/api/v1/auctions?limit=50`);
    await benchmark('Get Auctions (Paginated)', `${BASE_URL}/api/v1/auctions?limit=20&page=2`);

    // Filtering
    await benchmark('Filter by Brand', `${BASE_URL}/api/v1/auctions?brand=Hermes%20Birkin&limit=50`);
    await benchmark('Filter by Price Range', `${BASE_URL}/api/v1/auctions?min_price=5000&max_price=20000&limit=50`);

    // Sorting
    await benchmark('Sort by Price', `${BASE_URL}/api/v1/auctions?sort=price&order=asc&limit=50`);
    await benchmark('Sort by Savings', `${BASE_URL}/api/v1/auctions?sort=savings&order=desc&limit=50`);

    // Search
    await benchmark('Full-Text Search', `${BASE_URL}/api/v1/search?q=birkin&limit=50`);

    // Analytics
    await benchmark('Statistics', `${BASE_URL}/api/stats`);
    await benchmark('Best Values', `${BASE_URL}/api/best-values?limit=50`);
    await benchmark('Brand Analytics', `${BASE_URL}/api/insights/brand/Hermes%20Birkin`);

    // Complex queries
    await benchmark('Combined Filters', `${BASE_URL}/api/v1/auctions?brand=Chanel&min_price=1000&max_price=10000&sort=savings&limit=50`);

    // Cache test (same endpoint twice)
    console.log('\n🔄 Testing cache performance...');
    await benchmark('Stats (First Hit)', `${BASE_URL}/api/stats`, { requests: 5 });
    await benchmark('Stats (Cached)', `${BASE_URL}/api/stats`, { requests: 15 });

    // Generate report
    console.log('\n═══════════════════════════════════════════════════════');
    console.log('    BENCHMARK RESULTS');
    console.log('═══════════════════════════════════════════════════════');

    const report = benchmarks.report();

    console.log('\n📊 Summary:');
    console.log(`   Total Requests: ${report.summary.totalRequests}`);
    console.log(`   Success Rate: ${report.summary.successRate}`);
    console.log(`   Avg Response Time: ${report.summary.avgResponseTime}`);
    console.log(`   Throughput: ${report.summary.throughput}`);

    console.log('\n📈 Endpoint Performance:');
    console.log('┌─────────────────────────────────────────┬──────────┬─────┬─────┬─────┬─────┬─────┬─────┐');
    console.log('│ Endpoint                                │ Requests │ Avg │ Min │ Max │ P50 │ P95 │ P99 │');
    console.log('├─────────────────────────────────────────┼──────────┼─────┼─────┼─────┼─────┼─────┼─────┤');

    report.details.forEach(detail => {
      const endpoint = detail.endpoint.padEnd(39);
      const requests = detail.requests.toString().padStart(8);
      const avg = (detail.avg + 'ms').padStart(5);
      const min = (detail.min + 'ms').padStart(5);
      const max = (detail.max + 'ms').padStart(5);
      const p50 = (detail.p50 + 'ms').padStart(5);
      const p95 = (detail.p95 + 'ms').padStart(5);
      const p99 = (detail.p99 + 'ms').padStart(5);

      console.log(`│ ${endpoint} │ ${requests} │ ${avg} │ ${min} │ ${max} │ ${p50} │ ${p95} │ ${p99} │`);
    });

    console.log('└─────────────────────────────────────────┴──────────┴─────┴─────┴─────┴─────┴─────┴─────┘');

    // Performance grades
    console.log('\n🎯 Performance Grades:');
    report.details.forEach(detail => {
      let grade = 'A';
      let emoji = '🟢';

      if (detail.avg > 500) {
        grade = 'C';
        emoji = '🟡';
      } else if (detail.avg > 200) {
        grade = 'B';
        emoji = '🟢';
      }

      if (detail.avg > 1000) {
        grade = 'D';
        emoji = '🔴';
      }

      console.log(`   ${emoji} ${detail.endpoint.padEnd(40)} Grade: ${grade} (${detail.avg}ms avg)`);
    });

    // Save report to file
    const reportPath = path.join(__dirname, 'benchmark-results.json');
    fs.writeFileSync(reportPath, JSON.stringify({
      timestamp: new Date().toISOString(),
      baseUrl: BASE_URL,
      ...report
    }, null, 2));

    console.log(`\n📄 Full report saved to: ${reportPath}`);

    console.log('\n═══════════════════════════════════════════════════════');
    console.log('✅ Benchmark completed successfully!');
    console.log('═══════════════════════════════════════════════════════\n');

  } catch (error) {
    console.error('\n❌ Benchmark failed:', error.message);
    process.exit(1);
  }
}

// Run benchmarks
if (require.main === module) {
  runBenchmarks().catch(error => {
    console.error('Fatal error:', error);
    process.exit(1);
  });
}

module.exports = { benchmark, runBenchmarks };