const io = require('socket.io-client'); const axios = require('axios'); const { execSync } = require('child_process'); const yargs = require('yargs/yargs'); const { hideBin } = require('yargs/helpers'); const argv = yargs(hideBin(process.argv)) .option('trips', { alias: 't', type: 'number', description: 'Number of concurrent trips to simulate', default: 1 }) .option('base-url', { alias: 'b', type: 'string', description: 'Base URL for backend APIs', default: 'http://localhost/backend' }) .option('socket-url', { alias: 's', type: 'string', description: 'Location server socket URL', default: 'ws://localhost:2020' }) .argv; // Helper to get JWT for mock driver function getMockDriverJwt(driverId) { try { const output = execSync(`php generate_mock_data.php ${driverId}`).toString(); // Extract JSON portion in case of PHP warnings const jsonString = output.substring(output.indexOf('{')); return JSON.parse(jsonString).jwt; } catch (err) { console.error(`[Error] Failed to generate JWT for driver ${driverId}`, err.message); return 'mock_jwt'; } } let metrics = { successful: 0, failed: 0, connectionErrors: 0, apiErrors: 0, totalTime: 0 }; async function simulateTrip(tripIndex) { const driverId = 900000 + tripIndex; const passengerId = 800000 + tripIndex; const driverJwt = getMockDriverJwt(driverId); console.log(`[Trip ${tripIndex}] Starting... Driver: ${driverId}, Passenger: ${passengerId}`); const tripStartTime = Date.now(); // 1. Connect Driver to Socket // Note: socket.io usually expects http:// URL for the initial connection, even for websockets const socketUrl = argv.socketUrl.replace('ws://', 'http://').replace('wss://', 'https://'); const socket = io(socketUrl, { query: { driver_id: driverId, platform: 'android', jwt: driverJwt }, transports: ['websocket'], reconnection: false, timeout: 5000 }); return new Promise((resolve, reject) => { let rideId = null; socket.on('connect_error', (err) => { console.error(`[Trip ${tripIndex}] Socket Connection Error: ${err.message}. Is the Location Server running on ${socketUrl}?`); metrics.connectionErrors++; metrics.failed++; socket.disconnect(); resolve(false); }); socket.on('connect', async () => { console.log(`[Trip ${tripIndex}] Driver connected to socket.`); // 2. Passenger Requests Ride try { const addRideFormData = new URLSearchParams({ passenger_id: passengerId, start_location: '31.95,35.91', end_location: '31.96,35.92', price: '5', price_token: 'dummy', distance: '2', carType: 'Economy', passenger_name: 'Test Passenger', passenger_phone: '0790000000', start_name: 'Start', end_name: 'End' }); const reqStart = Date.now(); await axios.post(`${argv.baseUrl}/ride/rides/add_ride.php`, addRideFormData.toString(), { headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, timeout: 5000 }); console.log(`[Trip ${tripIndex}] Passenger requested ride. (API Latency: ${Date.now() - reqStart}ms)`); } catch (err) { console.error(`[Trip ${tripIndex}] API Error requesting ride:`, err.message); metrics.apiErrors++; metrics.failed++; socket.disconnect(); return resolve(false); } }); socket.on('market_new_ride', async (payload) => { rideId = payload.id; console.log(`[Trip ${tripIndex}] Received ride offer #${rideId}. Accepting...`); // 3. Driver Accepts Ride try { const acceptFormData = new URLSearchParams({ id: rideId, driver_id: driverId, status: 'accepted' }); const acceptStart = Date.now(); await axios.post(`${argv.baseUrl}/ride/rides/acceptRide.php`, acceptFormData.toString(), { headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, timeout: 5000 }); console.log(`[Trip ${tripIndex}] Accepted ride #${rideId}. (API Latency: ${Date.now() - acceptStart}ms)`); // 4. Simulate Driving (Updates Location) let steps = 0; const driveInterval = setInterval(async () => { steps++; socket.emit('update_location', { lat: 31.95 + (steps * 0.001), lng: 35.91 + (steps * 0.001), heading: 90, speed: 40, status: 'on' }); if (steps === 3) { const startFormData = new URLSearchParams({ id: rideId, driver_id: driverId, status: 'start' }); await axios.post(`${argv.baseUrl}/ride/rides/start_ride.php`, startFormData.toString()); console.log(`[Trip ${tripIndex}] Started ride #${rideId}`); } if (steps === 6) { clearInterval(driveInterval); // 5. Finish Ride const finishFormData = new URLSearchParams({ id: rideId, driver_id: driverId, status: 'finish' }); await axios.post(`${argv.baseUrl}/ride/rides/finish_ride_updates.php`, finishFormData.toString()); console.log(`[Trip ${tripIndex}] Finished ride #${rideId}.`); socket.disconnect(); metrics.successful++; resolve(true); } }, 1000); // 1-second interval to simulate fast movement } catch (err) { console.error(`[Trip ${tripIndex}] API Error during ride execution:`, err.message); metrics.apiErrors++; metrics.failed++; socket.disconnect(); resolve(false); } }); // Timeout fallback if market_new_ride never arrives setTimeout(() => { if (!rideId && socket.connected) { console.error(`[Trip ${tripIndex}] Timeout: Never received 'market_new_ride' via WebSocket.`); metrics.failed++; socket.disconnect(); resolve(false); } }, 15000); }); } async function runTest() { console.log(`\n======================================================`); console.log(`🚀 Starting Load Test with ${argv.trips} concurrent trips`); console.log(`📡 Backend API URL: ${argv.baseUrl}`); console.log(`🔌 Socket URL: ${argv.socketUrl}`); console.log(`======================================================\n`); const startTime = Date.now(); const promises = []; // Launch all simulated trips for (let i = 1; i <= argv.trips; i++) { promises.push(simulateTrip(i)); // Small delay between launches to prevent DDOSing localhost await new Promise(r => setTimeout(r, 50)); } await Promise.all(promises); const durationSec = ((Date.now() - startTime) / 1000).toFixed(2); const tps = (metrics.successful / durationSec).toFixed(2); console.log(`\n======================================================`); console.log(`📊 STRESS TEST RESULTS:`); console.log(`======================================================`); console.log(`⏱️ Total Duration: ${durationSec} seconds`); console.log(`⚡ Throughput: ${tps} trips / second`); console.log(`✅ Successful Trips: ${metrics.successful} / ${argv.trips}`); console.log(`❌ Failed Trips: ${metrics.failed} / ${argv.trips}`); if (metrics.failed > 0) { console.log(` - Socket Errors: ${metrics.connectionErrors}`); console.log(` - HTTP API Errors: ${metrics.apiErrors}`); } console.log(`======================================================\n`); process.exit(0); } runTest();