diff --git a/stress_test/load_test.js b/stress_test/load_test.js index 8ee9252b..b0206441 100644 --- a/stress_test/load_test.js +++ b/stress_test/load_test.js @@ -21,20 +21,37 @@ const argv = yargs(hideBin(process.argv)) alias: 's', type: 'string', description: 'Location server socket URL', - default: 'ws://localhost:2020' + 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}`).toString(); - // Extract JSON portion in case of PHP warnings + 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('{')); - return JSON.parse(jsonString).jwt; + 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 'mock_jwt'; + console.error(`[Error] Failed to generate JWT for driver ${driverId}:`, err.message); + return ''; } } @@ -43,6 +60,7 @@ let metrics = { failed: 0, connectionErrors: 0, apiErrors: 0, + jwtErrors: 0, totalTime: 0 }; @@ -51,40 +69,65 @@ async function simulateTrip(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 - // 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 socketUrl = argv.socketUrl; + debug(`Connecting to socket at: ${socketUrl}`); + const socket = io(socketUrl, { query: { - driver_id: driverId, + driver_id: String(driverId), platform: 'android', jwt: driverJwt }, reconnection: false, - timeout: 5000 + timeout: 10000, + forceNew: true }); - return new Promise((resolve, reject) => { + 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}. Is the Location Server running on ${socketUrl}?`); + console.error(`[Trip ${tripIndex}] ❌ Socket Connection Error: ${err.message}`); + debug(`Full error: ${JSON.stringify(err)}`); metrics.connectionErrors++; metrics.failed++; - socket.disconnect(); - resolve(false); + 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.`); + console.log(`[Trip ${tripIndex}] ✅ Driver connected to socket.`); // 2. Passenger Requests Ride try { const addRideFormData = new URLSearchParams({ - passenger_id: passengerId, + passenger_id: String(passengerId), start_location: '31.95,35.91', end_location: '31.96,35.92', price: '5', @@ -98,39 +141,47 @@ async function simulateTrip(tripIndex) { }); 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)`); + 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 requesting ride:`, err.message); + 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++; - socket.disconnect(); - return resolve(false); + finish(false); + return; } }); socket.on('market_new_ride', async (payload) => { - rideId = payload.id; - console.log(`[Trip ${tripIndex}] Received ride offer #${rideId}. Accepting...`); + rideId = payload.id || payload[0]?.id; + console.log(`[Trip ${tripIndex}] 📨 Received ride offer #${rideId}. Accepting...`); - // 3. Driver Accepts Ride try { const acceptFormData = new URLSearchParams({ - id: rideId, - driver_id: driverId, + 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: 5000 - }); - console.log(`[Trip ${tripIndex}] Accepted ride #${rideId}. (API Latency: ${Date.now() - acceptStart}ms)`); + 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; @@ -145,77 +196,141 @@ async function simulateTrip(tripIndex) { }); 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}`); + 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); - // 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}.`); + 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}`); + } - socket.disconnect(); metrics.successful++; - resolve(true); + finish(true); } - }, 1000); // 1-second interval to simulate fast movement + }, 1000); } catch (err) { - console.error(`[Trip ${tripIndex}] API Error during ride execution:`, err.message); + console.error(`[Trip ${tripIndex}] ❌ API Error (acceptRide): ${err.message}`); metrics.apiErrors++; metrics.failed++; - socket.disconnect(); - resolve(false); + finish(false); } }); - // Timeout fallback if market_new_ride never arrives + socket.on('disconnect', (reason) => { + debug(`[Trip ${tripIndex}] Disconnected: ${reason}`); + }); + + // Timeout fallback setTimeout(() => { - if (!rideId && socket.connected) { - console.error(`[Trip ${tripIndex}] Timeout: Never received 'market_new_ride' via WebSocket.`); + if (!resolved) { + console.error(`[Trip ${tripIndex}] ⏰ Timeout: No 'market_new_ride' received within 20s.`); metrics.failed++; - socket.disconnect(); - resolve(false); + finish(false); } - }, 15000); + }, 20000); }); } async function runTest() { + // ── Pre-flight checks ── 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`); + 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 = []; - // 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); + const tps = metrics.successful > 0 ? (metrics.successful / durationSec).toFixed(2) : '0.00'; console.log(`\n======================================================`); - console.log(`📊 STRESS TEST RESULTS:`); + 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}`); + 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(` - Socket Errors: ${metrics.connectionErrors}`); - console.log(` - HTTP API Errors: ${metrics.apiErrors}`); + console.log(` ├─ JWT Errors: ${metrics.jwtErrors}`); + console.log(` ├─ Socket Errors: ${metrics.connectionErrors}`); + console.log(` └─ API Errors: ${metrics.apiErrors}`); } console.log(`======================================================\n`); process.exit(0);