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: 'http://localhost:2020' }) .option('debug', { alias: 'd', type: 'boolean', description: 'Enable debug logging', default: false }) .argv; function debug(msg) { if (argv.debug) console.log(` [DEBUG] ${msg}`); } // Helper to get JWT for mock driver function getMockDriverJwt(driverId) { try { const output = execSync(`php generate_mock_data.php ${driverId}`, { timeout: 5000 }).toString(); debug(`JWT output for driver ${driverId}: ${output.substring(0, 80)}...`); const jsonString = output.substring(output.indexOf('{')); const parsed = JSON.parse(jsonString); if (!parsed.jwt) { console.error(`[Error] JWT generation returned empty for driver ${driverId}: ${output}`); return ''; } return parsed.jwt; } catch (err) { console.error(`[Error] Failed to generate JWT for driver ${driverId}:`, err.message); return ''; } } let metrics = { successful: 0, failed: 0, connectionErrors: 0, apiErrors: 0, jwtErrors: 0, totalTime: 0 }; async function simulateTrip(tripIndex) { const driverId = 900000 + tripIndex; const passengerId = 800000 + tripIndex; const driverJwt = getMockDriverJwt(driverId); if (!driverJwt) { console.error(`[Trip ${tripIndex}] Skipping — JWT generation failed for driver ${driverId}`); metrics.jwtErrors++; metrics.failed++; return false; } console.log(`[Trip ${tripIndex}] Starting... Driver: ${driverId}, Passenger: ${passengerId}`); debug(`JWT token (first 30 chars): ${driverJwt.substring(0, 30)}...`); const tripStartTime = Date.now(); // 1. Connect Driver to Socket const socketUrl = argv.socketUrl; debug(`Connecting to socket at: ${socketUrl}`); const socket = io(socketUrl, { query: { driver_id: String(driverId), platform: 'android', jwt: driverJwt }, reconnection: false, timeout: 10000, forceNew: true }); return new Promise((resolve) => { let rideId = null; let resolved = false; function finish(success) { if (resolved) return; resolved = true; try { socket.disconnect(); } catch(e) {} resolve(success); } socket.on('connect_error', (err) => { console.error(`[Trip ${tripIndex}] ❌ Socket Connection Error: ${err.message}`); debug(`Full error: ${JSON.stringify(err)}`); metrics.connectionErrors++; metrics.failed++; finish(false); }); socket.on('error', (err) => { console.error(`[Trip ${tripIndex}] ❌ Socket Error: ${err}`); metrics.connectionErrors++; metrics.failed++; finish(false); }); socket.on('connect', async () => { console.log(`[Trip ${tripIndex}] ✅ Driver connected to socket.`); // 2. Passenger Requests Ride try { const addRideFormData = new URLSearchParams({ passenger_id: String(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(); const res = await axios.post( `${argv.baseUrl}/ride/rides/add_ride.php`, addRideFormData.toString(), { headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, timeout: 10000 } ); debug(`add_ride response: ${JSON.stringify(res.data).substring(0, 100)}`); console.log(`[Trip ${tripIndex}] Passenger requested ride. (API: ${Date.now() - reqStart}ms)`); } catch (err) { console.error(`[Trip ${tripIndex}] ❌ API Error (add_ride): ${err.message}`); if (err.response) debug(`Response: ${JSON.stringify(err.response.data).substring(0, 200)}`); metrics.apiErrors++; metrics.failed++; finish(false); return; } }); socket.on('market_new_ride', async (payload) => { rideId = payload.id || payload[0]?.id; console.log(`[Trip ${tripIndex}] 📨 Received ride offer #${rideId}. Accepting...`); try { const acceptFormData = new URLSearchParams({ id: String(rideId), driver_id: String(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: 10000 } ); console.log(`[Trip ${tripIndex}] Accepted ride #${rideId}. (API: ${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) { try { const startFormData = new URLSearchParams({ id: String(rideId), driver_id: String(driverId), status: 'start' }); await axios.post(`${argv.baseUrl}/ride/rides/start_ride.php`, startFormData.toString(), { timeout: 10000 }); console.log(`[Trip ${tripIndex}] Started ride #${rideId}`); } catch (e) { debug(`start_ride error: ${e.message}`); } } if (steps === 6) { clearInterval(driveInterval); try { const finishFormData = new URLSearchParams({ id: String(rideId), driver_id: String(driverId), status: 'finish' }); await axios.post(`${argv.baseUrl}/ride/rides/finish_ride_updates.php`, finishFormData.toString(), { timeout: 10000 }); console.log(`[Trip ${tripIndex}] ✅ Finished ride #${rideId}.`); } catch (e) { debug(`finish_ride error: ${e.message}`); } metrics.successful++; finish(true); } }, 1000); } catch (err) { console.error(`[Trip ${tripIndex}] ❌ API Error (acceptRide): ${err.message}`); metrics.apiErrors++; metrics.failed++; finish(false); } }); socket.on('disconnect', (reason) => { debug(`[Trip ${tripIndex}] Disconnected: ${reason}`); }); // Timeout fallback setTimeout(() => { if (!resolved) { console.error(`[Trip ${tripIndex}] ⏰ Timeout: No 'market_new_ride' received within 20s.`); metrics.failed++; finish(false); } }, 20000); }); } async function runTest() { // ── Pre-flight checks ── console.log(`\n======================================================`); console.log(`🚀 Siro Stress Test`); console.log(`======================================================`); console.log(`📡 Backend API: ${argv.baseUrl}`); console.log(`🔌 Socket URL: ${argv.socketUrl}`); console.log(`🔢 Trips: ${argv.trips}`); console.log(`🐛 Debug: ${argv.debug ? 'ON' : 'OFF'}`); console.log(`------------------------------------------------------`); // Test 1: Can we generate JWT? console.log(`\n🔑 Pre-flight: Testing JWT generation...`); const testJwt = getMockDriverJwt(999999); if (!testJwt) { console.error(`❌ FATAL: Cannot generate JWT tokens. Check that:\n` + ` - PHP is installed on this server\n` + ` - /home/location/env/.env or backend/.env has JWT_SECRET_KEY\n` + ` - loction_server/vendor/autoload.php exists (run composer install)`); process.exit(1); } console.log(` ✅ JWT generation works.`); // Test 2: Can we reach the socket server? console.log(`\n🔌 Pre-flight: Testing socket connection...`); const testResult = await new Promise((resolve) => { const testSocket = io(argv.socketUrl, { query: { driver_id: '999999', platform: 'android', jwt: testJwt }, reconnection: false, timeout: 5000, forceNew: true }); testSocket.on('connect', () => { console.log(` ✅ Socket connection successful!`); testSocket.disconnect(); resolve(true); }); testSocket.on('connect_error', (err) => { console.error(` ❌ Socket connection failed: ${err.message}`); console.error(` 💡 Make sure the Location Server (driver_socket.php) is running on ${argv.socketUrl}`); console.error(` 💡 Check with: ps aux | grep driver_socket`); testSocket.disconnect(); resolve(false); }); testSocket.on('error', (err) => { console.error(` ❌ Socket error: ${err}`); testSocket.disconnect(); resolve(false); }); setTimeout(() => { testSocket.disconnect(); resolve(false); }, 6000); }); if (!testResult) { console.error(`\n❌ Cannot proceed — socket server unreachable. Aborting.`); process.exit(1); } // ── Run the actual test ── console.log(`\n------------------------------------------------------`); console.log(`🏁 Starting ${argv.trips} concurrent trips...\n`); const startTime = Date.now(); const promises = []; for (let i = 1; i <= argv.trips; i++) { promises.push(simulateTrip(i)); await new Promise(r => setTimeout(r, 50)); } await Promise.all(promises); const durationSec = ((Date.now() - startTime) / 1000).toFixed(2); const tps = metrics.successful > 0 ? (metrics.successful / durationSec).toFixed(2) : '0.00'; console.log(`\n======================================================`); console.log(`📊 STRESS TEST RESULTS`); console.log(`======================================================`); console.log(`⏱️ Duration: ${durationSec}s`); console.log(`⚡ Throughput: ${tps} trips/sec`); console.log(`✅ Successful: ${metrics.successful} / ${argv.trips}`); console.log(`❌ Failed: ${metrics.failed} / ${argv.trips}`); if (metrics.failed > 0) { console.log(` ├─ JWT Errors: ${metrics.jwtErrors}`); console.log(` ├─ Socket Errors: ${metrics.connectionErrors}`); console.log(` └─ API Errors: ${metrics.apiErrors}`); } console.log(`======================================================\n`); process.exit(0); } runTest();