← back to Wine Finder Next
scripts/performance-test.js
239 lines
#!/usr/bin/env node
// Performance testing script for Wine Finder platform
const https = require('https');
const http = require('http');
const { performance } = require('perf_hooks');
const BASE_URL = 'http://45.61.58.125:7250';
const TEST_ENDPOINTS = [
'/',
'/membership',
'/performance',
'/api/featured',
'/api/membership/bottles',
'/api/performance/metrics',
];
class PerformanceTest {
constructor() {
this.results = [];
this.targetMetrics = {
initialLoad: 1000, // < 1s initial load
apiResponse: 100, // < 100ms API response
ttfb: 200, // Time to first byte < 200ms
};
}
async testEndpoint(url, name) {
const startTime = performance.now();
return new Promise((resolve) => {
const protocol = url.startsWith('https') ? https : http;
protocol.get(url, (res) => {
const ttfb = performance.now() - startTime;
let data = '';
res.on('data', chunk => {
data += chunk;
});
res.on('end', () => {
const totalTime = performance.now() - startTime;
const size = Buffer.byteLength(data, 'utf8');
const result = {
name,
url,
statusCode: res.statusCode,
ttfb: Math.round(ttfb),
totalTime: Math.round(totalTime),
size: size,
sizeKB: (size / 1024).toFixed(2),
headers: res.headers,
cacheStatus: res.headers['x-cache-status'] || 'MISS',
pass: totalTime < (name.includes('api') ? this.targetMetrics.apiResponse : this.targetMetrics.initialLoad),
};
this.results.push(result);
resolve(result);
});
}).on('error', (err) => {
const result = {
name,
url,
error: err.message,
pass: false,
};
this.results.push(result);
resolve(result);
});
});
}
async runLoadTest(url, concurrent = 10, total = 100) {
console.log(`\n📊 Load Testing: ${url}`);
console.log(`Concurrent: ${concurrent}, Total: ${total}`);
const results = [];
const startTime = performance.now();
for (let i = 0; i < total; i += concurrent) {
const batch = [];
for (let j = 0; j < concurrent && (i + j) < total; j++) {
batch.push(this.testEndpoint(url, `Request ${i + j + 1}`));
}
const batchResults = await Promise.all(batch);
results.push(...batchResults);
// Show progress
process.stdout.write(`Progress: ${Math.min(i + concurrent, total)}/${total}\r`);
}
const totalTime = performance.now() - startTime;
// Calculate statistics
const successful = results.filter(r => !r.error && r.statusCode === 200);
const responseTimes = successful.map(r => r.totalTime);
if (responseTimes.length > 0) {
responseTimes.sort((a, b) => a - b);
const stats = {
totalRequests: total,
successful: successful.length,
failed: total - successful.length,
totalTime: Math.round(totalTime),
requestsPerSecond: (total / (totalTime / 1000)).toFixed(2),
avgResponseTime: Math.round(responseTimes.reduce((a, b) => a + b, 0) / responseTimes.length),
minResponseTime: responseTimes[0],
maxResponseTime: responseTimes[responseTimes.length - 1],
p50: responseTimes[Math.floor(responseTimes.length * 0.5)],
p95: responseTimes[Math.floor(responseTimes.length * 0.95)],
p99: responseTimes[Math.floor(responseTimes.length * 0.99)],
};
console.log('\n\n📈 Load Test Results:');
console.log('─'.repeat(40));
console.log(`Total Requests: ${stats.totalRequests}`);
console.log(`Successful: ${stats.successful} (${(stats.successful/stats.totalRequests*100).toFixed(1)}%)`);
console.log(`Failed: ${stats.failed}`);
console.log(`Total Time: ${stats.totalTime}ms`);
console.log(`Requests/sec: ${stats.requestsPerSecond}`);
console.log('\n📊 Response Times:');
console.log(`Average: ${stats.avgResponseTime}ms`);
console.log(`Min: ${stats.minResponseTime}ms`);
console.log(`Max: ${stats.maxResponseTime}ms`);
console.log(`P50: ${stats.p50}ms`);
console.log(`P95: ${stats.p95}ms`);
console.log(`P99: ${stats.p99}ms`);
return stats;
}
return null;
}
async runTests() {
console.log('🚀 Wine Finder Performance Test Suite');
console.log('=====================================\n');
console.log(`Target Metrics:`);
console.log(`- Initial Load: <${this.targetMetrics.initialLoad}ms`);
console.log(`- API Response: <${this.targetMetrics.apiResponse}ms`);
console.log(`- TTFB: <${this.targetMetrics.ttfb}ms\n`);
// Test individual endpoints
console.log('Testing Individual Endpoints...\n');
for (const endpoint of TEST_ENDPOINTS) {
const url = `${BASE_URL}${endpoint}`;
const result = await this.testEndpoint(url, endpoint);
const status = result.pass ? '✅' : '❌';
const time = result.totalTime ? `${result.totalTime}ms` : 'ERROR';
const size = result.sizeKB ? `${result.sizeKB}KB` : 'N/A';
const cache = result.cacheStatus || 'N/A';
console.log(`${status} ${endpoint.padEnd(30)} ${time.padEnd(10)} ${size.padEnd(10)} Cache: ${cache}`);
if (result.error) {
console.log(` Error: ${result.error}`);
}
}
// Load tests
console.log('\n\n🔥 Running Load Tests...');
// Test homepage
await this.runLoadTest(`${BASE_URL}/`, 10, 50);
// Test API endpoint
await this.runLoadTest(`${BASE_URL}/api/featured`, 20, 100);
// Generate report
this.generateReport();
}
generateReport() {
console.log('\n\n📝 PERFORMANCE REPORT');
console.log('='.repeat(50));
const passed = this.results.filter(r => r.pass).length;
const total = this.results.length;
const percentage = (passed / total * 100).toFixed(1);
console.log(`\n📊 Overall Results: ${passed}/${total} passed (${percentage}%)`);
// Find slow endpoints
const slowEndpoints = this.results
.filter(r => !r.pass && r.totalTime)
.sort((a, b) => b.totalTime - a.totalTime)
.slice(0, 5);
if (slowEndpoints.length > 0) {
console.log('\n⚠️ Slowest Endpoints:');
slowEndpoints.forEach(ep => {
console.log(` - ${ep.name}: ${ep.totalTime}ms`);
});
}
// Recommendations
console.log('\n💡 Recommendations:');
const apiEndpoints = this.results.filter(r => r.name && r.name.includes('api'));
const avgApiTime = apiEndpoints.reduce((sum, r) => sum + (r.totalTime || 0), 0) / apiEndpoints.length;
if (avgApiTime > this.targetMetrics.apiResponse) {
console.log(` - API responses averaging ${Math.round(avgApiTime)}ms (target: ${this.targetMetrics.apiResponse}ms)`);
console.log(' Consider implementing Redis caching for frequently accessed data');
}
const pageEndpoints = this.results.filter(r => r.name && !r.name.includes('api'));
const avgPageTime = pageEndpoints.reduce((sum, r) => sum + (r.totalTime || 0), 0) / pageEndpoints.length;
if (avgPageTime > this.targetMetrics.initialLoad) {
console.log(` - Page loads averaging ${Math.round(avgPageTime)}ms (target: ${this.targetMetrics.initialLoad}ms)`);
console.log(' Consider code splitting and lazy loading for heavy components');
}
const uncachedEndpoints = this.results.filter(r => r.cacheStatus === 'MISS');
if (uncachedEndpoints.length > this.results.length * 0.5) {
console.log(' - Most responses are not cached');
console.log(' Implement CDN and browser caching strategies');
}
if (percentage === '100.0') {
console.log(' - 🎉 All performance targets met! Great job!');
}
console.log('\n' + '='.repeat(50));
console.log('Test completed at:', new Date().toLocaleString());
}
}
// Run tests
const tester = new PerformanceTest();
tester.runTests().catch(console.error);