diff --git a/agreement.js b/agreement.js index eed116d..f23e18d 100644 --- a/agreement.js +++ b/agreement.js @@ -7,7 +7,7 @@ class Agreement { this.urls = urls this.server = server this.state = "new" - this.timeout = timeout || 30 + this.timeout = timeout || 10 } startTimeout(fn) { diff --git a/config.json b/config.json index 3786164..713fdca 100644 --- a/config.json +++ b/config.json @@ -13,6 +13,7 @@ { "name": "public_goods_game", "enabled": true, + "agreementTimeout": 1, "scheduler": { "type": "GatScheduler", "params": { diff --git a/server.js b/server.js index a6fb36b..68412be 100644 --- a/server.js +++ b/server.js @@ -298,6 +298,7 @@ function startReadyGames(experiments, agreementIds, usersDb) { gameUsersIds, conditionObject.users.map((u) => u.redirectedUrl), conditionObject.server, + experiment.agreementTimeout, ) console.log( `New agreement: ${agreement.agreementId} ${JSON.stringify(gameUsersIds)}.`, @@ -395,6 +396,7 @@ async function main() { if (!SchedulerPlugins.classExists(e.scheduler.type)) { throw new Error(`Class ${e.scheduler.type} not found!`) } + experiments[e.name]["agreementTimeout"] = e.agreementTimeout // Instantiate a scheduler class and pass the queue to // be managed by the scheduler experiments[e.name]["scheduler"] = new (SchedulerPlugins.getClass( @@ -615,10 +617,12 @@ async function main() { // } if (user.state === "waitAgreement") { // User is waiting in an agreement + // console.log(`${userId} in state ${user.state}.`) return } if (user.state === "agreed") { // User is waiting in an agreement + // console.log(`${userId} in state ${user.state}.`) return } if (user.state === "inoTreePages") { @@ -626,6 +630,7 @@ async function main() { socket.emit("gameStart", { room: user.redirectedUrl.toString() }) return } + // console.log(`${userId} in state ${user.state}.`) // console.log(`NEw user: ${user.userId} ${user.state}`) user.addListenerForState("queued", async (user, state) => { diff --git a/server_test/README.md b/server_test/README.md new file mode 100644 index 0000000..0fdea1f --- /dev/null +++ b/server_test/README.md @@ -0,0 +1,13 @@ +## Load test server + +Run the server + +```bash +npm run dev-newDb +``` + +Run testing script with N users + +```bash +node test_user_flow.js N +``` diff --git a/server_test/scan_db.js b/server_test/scan_db.js new file mode 100644 index 0000000..a7337cc --- /dev/null +++ b/server_test/scan_db.js @@ -0,0 +1,34 @@ +const userdb = require("../data/userdb.json") + +const maxUsers = 999 + +const userIds = [] + +for (let i = 0; i < maxUsers; i++) { + const base = 1000 + userIds.push(base + i) +} + +usersInDb = userdb.map((u) => { + return u.userId +}) + +const A = new Set(userIds) +const B = new Set(usersInDb) + +const symmetricDifference = (setA, setB) => { + const difference = new Set([...setA].filter((x) => !setB.has(x))) + ;[...setB].forEach((x) => { + if (!setA.has(x)) { + difference.add(x) + } + }) + return difference +} + +// Example usage +// const set1 = new Set([1, 2, 3, 4]); +// const set2 = new Set([3, 4, 5, 6]); + +const symDiff = symmetricDifference(A, B) +console.log(symDiff) // Output: Set { 1, 2, 5, 6 } diff --git a/server_test/test_db_writes.js b/server_test/test_db_writes.js deleted file mode 100644 index 3b2c5a6..0000000 --- a/server_test/test_db_writes.js +++ /dev/null @@ -1,22 +0,0 @@ -//const fs = require('fs') -const db = require("../data/userdb.json") -const redirectedUsers = require("./redirected_users.json") -// -//const dbFile = '../data/userdb.json' - -//const db = JSON.parse(fs.readFileSync(dbFile)) - -//console.log(db) -//console.log(redirectedUsers) - -const userIds = db.map((u) => { - return u.userId -}) - -redirectedUsers.forEach((u) => { - if (!userIds.includes(u)) { - console.log(`${u} not in DB`) - } else { - //console.log(u) - } -}) diff --git a/server_test/test_user_flow.js b/server_test/test_user_flow.js index 8526c82..92bfb5b 100644 --- a/server_test/test_user_flow.js +++ b/server_test/test_user_flow.js @@ -5,7 +5,7 @@ function randomBetween(min, max) { return Math.floor(Math.random() * (max - min) + min) } -const maxUsers = 300 +const maxUsers = parseInt(process.argv[2]) ? parseInt(process.argv[2]) : 100 const experimentId = "public_goods_game" const url = "http://localhost:8060" const virtUsers = {} @@ -19,28 +19,8 @@ for (let i = 0; i < maxUsers; i++) { Object.values(virtUsers).forEach((vu) => { vu.connect().then(() => { + // true: random agree or not agree + // false: always agree vu.attemptQueueFlow(true) }) }) - -// setInterval(() => { -// const ids = Object.values(virtUsers).filter(vu => { -// return true -// if (vu.state == "redirected"){ -// return true -// } -// }).map(vu => { -// return { -// userId: vu.userId, -// url: vu.redirectUrl -// } -// }) -// -// const idsString = JSON.stringify(ids, null, 2) -// //console.log("saving...", idsString) -// fs.writeFile('redirected_users.json', idsString, (err) => { -// if (err) { -// console.log('Error ', err) -// } -// }) -// }, 2000) diff --git a/server_test/virtual-user-ws.js b/server_test/virtual-user-ws.js index 5f5f35a..f08bd07 100644 --- a/server_test/virtual-user-ws.js +++ b/server_test/virtual-user-ws.js @@ -16,9 +16,15 @@ class VirtualUser { this.socket = io(this.serverUrl) this.socket.on("connect", () => { this.flag = 0 - // console.log(`[${this.userId}] connected to ${this.serverUrl}`) + // console.log(`[${this.userId}] connected.`) + this.state = "connected" resolve() }) + this.socket.on("connect_error", (err) => { + // console.log(`[${this.userId}] error connection.`) + this.sate = "error" + // reject(err) + }) }) } #setupSocketEvents() { @@ -30,10 +36,12 @@ class VirtualUser { return } this.socket.on("wait", (data) => { + // console.log(`[${this.userId}] received wait.`) this.flag = 0 this.state = "queued" }) this.socket.on("queueUpdate", (data) => { + // console.log(`[${this.userId}] received queueUpdate.`) this.flag = 0 // Nothing to do }) @@ -55,29 +63,40 @@ class VirtualUser { if (this.state == "redirected") { return } - // console.log(`${this.userId} emmiting landingPage`) this.socket.emit("landingPage", { experimentId: this.experimentId, userId: this.userId, }) - // console.log(`${this.userId} emmiting newUser`) this.socket.emit("newUser", { experimentId: this.experimentId, userId: this.userId, }) - const that = this + const intervatl = setInterval(() => { - if (that.state == "redirected" || !that.socket) { + // If user is stuck (server overload) restart flow + if (this.state == "connected" || this.state == "error") { + this.socket.close() clearInterval(intervatl) + // console.log(`[${this.userId}] reconnecting.`) + this.socket = io(this.serverUrl) + this.attemptQueueFlow(true) return } - if (that.flag > 0) { - that.socket.emit("newUser", { - experimentId: that.experimentId, - userId: that.userId, + // If redirected clear interval and quit user flow + if (this.state == "redirected" || !this.socket) { + clearInterval(intervatl) + return + } + // Poke server to force requeue if for some reason user + // dropped out of queue + if (this.flag > 0) { + this.socket.emit("newUser", { + experimentId: this.experimentId, + userId: this.userId, }) } else { - that.flag += 1 + // console.log(`[${this.userId}] in stuck interval.`) + this.flag += 1 } }, 5000) } @@ -85,7 +104,6 @@ class VirtualUser { #flipCoin() { // Generate a random number between 0 and 1 const randomNumber = Math.random() - // Return "Heads" if the number is less than 0.5, otherwise return "Tails" return randomNumber < 0.5 } @@ -93,14 +111,14 @@ class VirtualUser { this.#setupSocketEvents() this.socket.on("agree", (data) => { + // console.log(`[${this.userId}] received agree.`) this.flag = 0 + // Choose to agree or not if (random && this.#flipCoin()) { - // console.log(`${this.userId} ignoring agreement`) + // do not agree and wait for timeout return } - // console.log(`${this.userId} accepting agreement`) const uuid = data.uuid - // Choose to agree or not this.socket.emit("userAgreed", { experimentId: this.experimentId, userId: this.userId, @@ -110,9 +128,9 @@ class VirtualUser { }) this.socket.on("reset", (data) => { + // console.log(`[${this.userId}] received reset.`) this.flag = 0 this.state = "startedPage" - // console.log(`${this.userId} emmiting newUser`) this.socket.emit("newUser", { experimentId: this.experimentId, userId: this.userId,