225 lines
8.5 KiB
JavaScript
225 lines
8.5 KiB
JavaScript
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
|
|
},
|
|
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();
|