Update: 2026-07-17 00:23:48
This commit is contained in:
+183
-68
@@ -21,20 +21,37 @@ const argv = yargs(hideBin(process.argv))
|
|||||||
alias: 's',
|
alias: 's',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'Location server socket URL',
|
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;
|
.argv;
|
||||||
|
|
||||||
|
function debug(msg) {
|
||||||
|
if (argv.debug) console.log(` [DEBUG] ${msg}`);
|
||||||
|
}
|
||||||
|
|
||||||
// Helper to get JWT for mock driver
|
// Helper to get JWT for mock driver
|
||||||
function getMockDriverJwt(driverId) {
|
function getMockDriverJwt(driverId) {
|
||||||
try {
|
try {
|
||||||
const output = execSync(`php generate_mock_data.php ${driverId}`).toString();
|
const output = execSync(`php generate_mock_data.php ${driverId}`, {
|
||||||
// Extract JSON portion in case of PHP warnings
|
timeout: 5000
|
||||||
|
}).toString();
|
||||||
|
debug(`JWT output for driver ${driverId}: ${output.substring(0, 80)}...`);
|
||||||
const jsonString = output.substring(output.indexOf('{'));
|
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) {
|
} catch (err) {
|
||||||
console.error(`[Error] Failed to generate JWT for driver ${driverId}`, err.message);
|
console.error(`[Error] Failed to generate JWT for driver ${driverId}:`, err.message);
|
||||||
return 'mock_jwt';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,6 +60,7 @@ let metrics = {
|
|||||||
failed: 0,
|
failed: 0,
|
||||||
connectionErrors: 0,
|
connectionErrors: 0,
|
||||||
apiErrors: 0,
|
apiErrors: 0,
|
||||||
|
jwtErrors: 0,
|
||||||
totalTime: 0
|
totalTime: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -51,40 +69,65 @@ async function simulateTrip(tripIndex) {
|
|||||||
const passengerId = 800000 + tripIndex;
|
const passengerId = 800000 + tripIndex;
|
||||||
const driverJwt = getMockDriverJwt(driverId);
|
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}`);
|
console.log(`[Trip ${tripIndex}] Starting... Driver: ${driverId}, Passenger: ${passengerId}`);
|
||||||
|
debug(`JWT token (first 30 chars): ${driverJwt.substring(0, 30)}...`);
|
||||||
const tripStartTime = Date.now();
|
const tripStartTime = Date.now();
|
||||||
|
|
||||||
// 1. Connect Driver to Socket
|
// 1. Connect Driver to Socket
|
||||||
// Note: socket.io usually expects http:// URL for the initial connection, even for websockets
|
const socketUrl = argv.socketUrl;
|
||||||
const socketUrl = argv.socketUrl.replace('ws://', 'http://').replace('wss://', 'https://');
|
debug(`Connecting to socket at: ${socketUrl}`);
|
||||||
|
|
||||||
const socket = io(socketUrl, {
|
const socket = io(socketUrl, {
|
||||||
query: {
|
query: {
|
||||||
driver_id: driverId,
|
driver_id: String(driverId),
|
||||||
platform: 'android',
|
platform: 'android',
|
||||||
jwt: driverJwt
|
jwt: driverJwt
|
||||||
},
|
},
|
||||||
reconnection: false,
|
reconnection: false,
|
||||||
timeout: 5000
|
timeout: 10000,
|
||||||
|
forceNew: true
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve) => {
|
||||||
let rideId = null;
|
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) => {
|
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.connectionErrors++;
|
||||||
metrics.failed++;
|
metrics.failed++;
|
||||||
socket.disconnect();
|
finish(false);
|
||||||
resolve(false);
|
});
|
||||||
|
|
||||||
|
socket.on('error', (err) => {
|
||||||
|
console.error(`[Trip ${tripIndex}] ❌ Socket Error: ${err}`);
|
||||||
|
metrics.connectionErrors++;
|
||||||
|
metrics.failed++;
|
||||||
|
finish(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('connect', async () => {
|
socket.on('connect', async () => {
|
||||||
console.log(`[Trip ${tripIndex}] Driver connected to socket.`);
|
console.log(`[Trip ${tripIndex}] ✅ Driver connected to socket.`);
|
||||||
|
|
||||||
// 2. Passenger Requests Ride
|
// 2. Passenger Requests Ride
|
||||||
try {
|
try {
|
||||||
const addRideFormData = new URLSearchParams({
|
const addRideFormData = new URLSearchParams({
|
||||||
passenger_id: passengerId,
|
passenger_id: String(passengerId),
|
||||||
start_location: '31.95,35.91',
|
start_location: '31.95,35.91',
|
||||||
end_location: '31.96,35.92',
|
end_location: '31.96,35.92',
|
||||||
price: '5',
|
price: '5',
|
||||||
@@ -98,39 +141,47 @@ async function simulateTrip(tripIndex) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const reqStart = Date.now();
|
const reqStart = Date.now();
|
||||||
await axios.post(`${argv.baseUrl}/ride/rides/add_ride.php`, addRideFormData.toString(), {
|
const res = await axios.post(
|
||||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
`${argv.baseUrl}/ride/rides/add_ride.php`,
|
||||||
timeout: 5000
|
addRideFormData.toString(),
|
||||||
});
|
{
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
console.log(`[Trip ${tripIndex}] Passenger requested ride. (API Latency: ${Date.now() - reqStart}ms)`);
|
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) {
|
} 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.apiErrors++;
|
||||||
metrics.failed++;
|
metrics.failed++;
|
||||||
socket.disconnect();
|
finish(false);
|
||||||
return resolve(false);
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('market_new_ride', async (payload) => {
|
socket.on('market_new_ride', async (payload) => {
|
||||||
rideId = payload.id;
|
rideId = payload.id || payload[0]?.id;
|
||||||
console.log(`[Trip ${tripIndex}] Received ride offer #${rideId}. Accepting...`);
|
console.log(`[Trip ${tripIndex}] 📨 Received ride offer #${rideId}. Accepting...`);
|
||||||
|
|
||||||
// 3. Driver Accepts Ride
|
|
||||||
try {
|
try {
|
||||||
const acceptFormData = new URLSearchParams({
|
const acceptFormData = new URLSearchParams({
|
||||||
id: rideId,
|
id: String(rideId),
|
||||||
driver_id: driverId,
|
driver_id: String(driverId),
|
||||||
status: 'accepted'
|
status: 'accepted'
|
||||||
});
|
});
|
||||||
|
|
||||||
const acceptStart = Date.now();
|
const acceptStart = Date.now();
|
||||||
await axios.post(`${argv.baseUrl}/ride/rides/acceptRide.php`, acceptFormData.toString(), {
|
await axios.post(
|
||||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
`${argv.baseUrl}/ride/rides/acceptRide.php`,
|
||||||
timeout: 5000
|
acceptFormData.toString(),
|
||||||
});
|
{
|
||||||
console.log(`[Trip ${tripIndex}] Accepted ride #${rideId}. (API Latency: ${Date.now() - acceptStart}ms)`);
|
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)
|
// 4. Simulate Driving (Updates Location)
|
||||||
let steps = 0;
|
let steps = 0;
|
||||||
@@ -145,77 +196,141 @@ async function simulateTrip(tripIndex) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (steps === 3) {
|
if (steps === 3) {
|
||||||
const startFormData = new URLSearchParams({ id: rideId, driver_id: driverId, status: 'start' });
|
try {
|
||||||
await axios.post(`${argv.baseUrl}/ride/rides/start_ride.php`, startFormData.toString());
|
const startFormData = new URLSearchParams({ id: String(rideId), driver_id: String(driverId), status: 'start' });
|
||||||
console.log(`[Trip ${tripIndex}] Started ride #${rideId}`);
|
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) {
|
if (steps === 6) {
|
||||||
clearInterval(driveInterval);
|
clearInterval(driveInterval);
|
||||||
// 5. Finish Ride
|
try {
|
||||||
const finishFormData = new URLSearchParams({ id: rideId, driver_id: driverId, status: 'finish' });
|
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());
|
await axios.post(`${argv.baseUrl}/ride/rides/finish_ride_updates.php`, finishFormData.toString(), { timeout: 10000 });
|
||||||
console.log(`[Trip ${tripIndex}] Finished ride #${rideId}.`);
|
console.log(`[Trip ${tripIndex}] ✅ Finished ride #${rideId}.`);
|
||||||
|
} catch (e) {
|
||||||
|
debug(`finish_ride error: ${e.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
socket.disconnect();
|
|
||||||
metrics.successful++;
|
metrics.successful++;
|
||||||
resolve(true);
|
finish(true);
|
||||||
}
|
}
|
||||||
}, 1000); // 1-second interval to simulate fast movement
|
}, 1000);
|
||||||
|
|
||||||
} catch (err) {
|
} 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.apiErrors++;
|
||||||
metrics.failed++;
|
metrics.failed++;
|
||||||
socket.disconnect();
|
finish(false);
|
||||||
resolve(false);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Timeout fallback if market_new_ride never arrives
|
socket.on('disconnect', (reason) => {
|
||||||
|
debug(`[Trip ${tripIndex}] Disconnected: ${reason}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Timeout fallback
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!rideId && socket.connected) {
|
if (!resolved) {
|
||||||
console.error(`[Trip ${tripIndex}] Timeout: Never received 'market_new_ride' via WebSocket.`);
|
console.error(`[Trip ${tripIndex}] ⏰ Timeout: No 'market_new_ride' received within 20s.`);
|
||||||
metrics.failed++;
|
metrics.failed++;
|
||||||
socket.disconnect();
|
finish(false);
|
||||||
resolve(false);
|
|
||||||
}
|
}
|
||||||
}, 15000);
|
}, 20000);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function runTest() {
|
async function runTest() {
|
||||||
|
// ── Pre-flight checks ──
|
||||||
console.log(`\n======================================================`);
|
console.log(`\n======================================================`);
|
||||||
console.log(`🚀 Starting Load Test with ${argv.trips} concurrent trips`);
|
console.log(`🚀 Siro Stress Test`);
|
||||||
console.log(`📡 Backend API URL: ${argv.baseUrl}`);
|
console.log(`======================================================`);
|
||||||
console.log(`🔌 Socket URL: ${argv.socketUrl}`);
|
console.log(`📡 Backend API: ${argv.baseUrl}`);
|
||||||
console.log(`======================================================\n`);
|
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 startTime = Date.now();
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
|
||||||
// Launch all simulated trips
|
|
||||||
for (let i = 1; i <= argv.trips; i++) {
|
for (let i = 1; i <= argv.trips; i++) {
|
||||||
promises.push(simulateTrip(i));
|
promises.push(simulateTrip(i));
|
||||||
// Small delay between launches to prevent DDOSing localhost
|
|
||||||
await new Promise(r => setTimeout(r, 50));
|
await new Promise(r => setTimeout(r, 50));
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
|
|
||||||
const durationSec = ((Date.now() - startTime) / 1000).toFixed(2);
|
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(`\n======================================================`);
|
||||||
console.log(`📊 STRESS TEST RESULTS:`);
|
console.log(`📊 STRESS TEST RESULTS`);
|
||||||
console.log(`======================================================`);
|
console.log(`======================================================`);
|
||||||
console.log(`⏱️ Total Duration: ${durationSec} seconds`);
|
console.log(`⏱️ Duration: ${durationSec}s`);
|
||||||
console.log(`⚡ Throughput: ${tps} trips / second`);
|
console.log(`⚡ Throughput: ${tps} trips/sec`);
|
||||||
console.log(`✅ Successful Trips: ${metrics.successful} / ${argv.trips}`);
|
console.log(`✅ Successful: ${metrics.successful} / ${argv.trips}`);
|
||||||
console.log(`❌ Failed Trips: ${metrics.failed} / ${argv.trips}`);
|
console.log(`❌ Failed: ${metrics.failed} / ${argv.trips}`);
|
||||||
if (metrics.failed > 0) {
|
if (metrics.failed > 0) {
|
||||||
console.log(` - Socket Errors: ${metrics.connectionErrors}`);
|
console.log(` ├─ JWT Errors: ${metrics.jwtErrors}`);
|
||||||
console.log(` - HTTP API Errors: ${metrics.apiErrors}`);
|
console.log(` ├─ Socket Errors: ${metrics.connectionErrors}`);
|
||||||
|
console.log(` └─ API Errors: ${metrics.apiErrors}`);
|
||||||
}
|
}
|
||||||
console.log(`======================================================\n`);
|
console.log(`======================================================\n`);
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
|
|||||||
Reference in New Issue
Block a user