snapshot of https://github.com/raimohanska/ourboard.git (master, 2026-09-23; blobs > 131072B omitted)
This commit is contained in:
1
perf-tester/README.md
Normal file
1
perf-tester/README.md
Normal file
@@ -0,0 +1 @@
|
||||
Ourboard perftester. `yarn dev`. See src/index.ts for supported ENV vars.
|
||||
42
perf-tester/package.json
Normal file
42
perf-tester/package.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "rboard-perf-tester",
|
||||
"version": "1.0.0",
|
||||
"main": "dist/perf-tester/src/index.js",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/cookie": "^0.4.0",
|
||||
"@types/email-validator": "^1.0.6",
|
||||
"@types/lodash": "^4.14.161",
|
||||
"@types/md5": "^2.2.1",
|
||||
"@types/node-fetch": "^2.5.9",
|
||||
"@types/pretty-ms": "^5.0.1",
|
||||
"cookie": "^0.4.1",
|
||||
"email-validator": "^2.0.4",
|
||||
"harmaja": "^0.24",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"lodash": "^4.17.20",
|
||||
"lonna": "^0.12.2",
|
||||
"material-design-icons-iconfont": "^6.1.0",
|
||||
"md5": "^2.3.0",
|
||||
"node-fetch": "^2.6.1",
|
||||
"pretty-ms": "^7.0.1",
|
||||
"uuid": "^8.3.0",
|
||||
"ws": "^7.4.4"
|
||||
},
|
||||
"scripts": {
|
||||
"watch": "tsc --watch --noEmit",
|
||||
"build": "tsc",
|
||||
"start": "node .",
|
||||
"create-boards": "tsx src/create-boards.ts",
|
||||
"dev": "tsc-watch --onSuccess \"node .\" --preserveWatchOutput"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jsonwebtoken": "^8.5.0",
|
||||
"@types/uuid": "^8.3.0",
|
||||
"cssnano": "^4.1.10",
|
||||
"nodemon": "^2.0.4",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"tsc-watch": "^4.2.9",
|
||||
"typescript": "^4.0.2"
|
||||
}
|
||||
}
|
||||
24
perf-tester/src/create-boards.ts
Normal file
24
perf-tester/src/create-boards.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import fetch from "node-fetch"
|
||||
import _ from "lodash"
|
||||
|
||||
const BOARD_COUNT = parseInt(process.env.BOARD_COUNT || "1")
|
||||
const DOMAIN = process.env.DOMAIN
|
||||
const API_ROOT = `${DOMAIN ? "https" : "http"}://${DOMAIN ?? "localhost:1337"}`
|
||||
const CREATE_BOARD_API = `${API_ROOT}/api/v1/board`
|
||||
|
||||
async function createBoardAndReturnId(n: number) {
|
||||
const result = await fetch(CREATE_BOARD_API, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ name: "perftest" + n }),
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
},
|
||||
})
|
||||
const body = await result.json()
|
||||
return body.id as string
|
||||
}
|
||||
|
||||
const promises = _.range(1, BOARD_COUNT + 1).map(createBoardAndReturnId)
|
||||
Promise.all(promises).then((ids) => {
|
||||
console.log(ids.join(","))
|
||||
})
|
||||
170
perf-tester/src/index.ts
Normal file
170
perf-tester/src/index.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
import dotenv from "dotenv"
|
||||
dotenv.config({ path: "../backend/.env" })
|
||||
|
||||
import _ from "lodash"
|
||||
import * as L from "lonna"
|
||||
import WebSocket from "ws"
|
||||
import { NOTE_COLORS } from "../../common/src/colors"
|
||||
import {
|
||||
CrdtEnabled,
|
||||
Point,
|
||||
UIEvent,
|
||||
defaultBoardSize,
|
||||
isNote,
|
||||
isPersistableBoardItemEvent,
|
||||
isText,
|
||||
newNote,
|
||||
newText,
|
||||
} from "../../common/src/domain"
|
||||
import { CRDTStore } from "../../frontend/src/store/crdt-store"
|
||||
import { GenericServerConnection } from "../../frontend/src/store/server-connection"
|
||||
|
||||
// hack, sue me
|
||||
// @ts-ignore
|
||||
global.localStorage = {}
|
||||
|
||||
function add(a: Point, b: Point) {
|
||||
return { x: a.x + b.x, y: a.y + b.y }
|
||||
}
|
||||
|
||||
function createTester(nickname: string, boardId: string) {
|
||||
let counter = 0
|
||||
const { width, height } = defaultBoardSize
|
||||
const center = { x: width / 2 - 30 + Math.random() * 60, y: height / 2 - 20 + Math.random() * 40 }
|
||||
const radius = 10 + Math.random() * 10
|
||||
const increment = Math.random() * 4 - 2
|
||||
const WS_ROOT = `${DOMAIN ? "wss" : "ws"}://${DOMAIN ?? "localhost:1337"}`
|
||||
const WS_ADDRESS = `${WS_ROOT}/socket/board/${boardId}`
|
||||
|
||||
let connection = GenericServerConnection(L.constant(WS_ADDRESS), L.constant(false), (s) => new WebSocket(s) as any)
|
||||
let sessionId = ""
|
||||
connection.bufferedServerEvents.forEach((event) => {
|
||||
if (event.action === "board.join.ack") {
|
||||
console.log("Got session id", sessionId)
|
||||
sessionId = event.sessionId
|
||||
}
|
||||
})
|
||||
const localEvents = L.bus<UIEvent>()
|
||||
localEvents.forEach(connection.send)
|
||||
|
||||
class MyWebSocket extends WebSocket {
|
||||
constructor(url: string | URL, protocols?: string | string[] | undefined) {
|
||||
console.log("Creating websocket", url, protocols)
|
||||
super(url as any, protocols, { headers: { Cookie: `sessionId=${sessionId}` } })
|
||||
}
|
||||
}
|
||||
|
||||
const crdtStore = CRDTStore(
|
||||
L.constant(boardId),
|
||||
connection.connected,
|
||||
localEvents.pipe(L.filter(isPersistableBoardItemEvent)).applyScope(L.globalScope),
|
||||
L.constant({
|
||||
status: "anonymous",
|
||||
sessionId: null,
|
||||
nickname: nickname,
|
||||
nicknameSetByUser: true,
|
||||
loginSupported: false,
|
||||
}),
|
||||
() => WS_ROOT,
|
||||
MyWebSocket as any,
|
||||
)
|
||||
crdtStore.getBoardCrdt(boardId)
|
||||
|
||||
connection.connected
|
||||
.pipe(
|
||||
L.changes,
|
||||
L.filter((c) => c),
|
||||
)
|
||||
.forEach(() => {
|
||||
localEvents.push({ action: "board.join", boardId })
|
||||
})
|
||||
connection.bufferedServerEvents.forEach((event) => {
|
||||
if (event.action === "board.init" && "board" in event) {
|
||||
const boardAtInit = event.board
|
||||
const notes = Object.values(boardAtInit.items).filter(isNote)
|
||||
const texts = Object.values(boardAtInit.items).filter(isText)
|
||||
setInterval(() => {
|
||||
counter += increment
|
||||
const position = add(center, {
|
||||
x: radius * Math.sin(counter / 100),
|
||||
y: radius * Math.cos(counter / 100),
|
||||
})
|
||||
localEvents.push({ action: "cursor.move", position, boardId })
|
||||
if (Math.random() < notesPerInterval) {
|
||||
const note = newNote("NOTE " + counter, "black", position.x, position.y)
|
||||
notes.push(note)
|
||||
localEvents.push({
|
||||
action: "item.add",
|
||||
boardId,
|
||||
items: [note],
|
||||
connections: [],
|
||||
})
|
||||
}
|
||||
if (Math.random() < textsPerInterval) {
|
||||
const text = newText(CrdtEnabled, "TEXT " + counter, position.x, position.y, 5, 5)
|
||||
localEvents.push({
|
||||
action: "item.add",
|
||||
boardId,
|
||||
items: [text],
|
||||
connections: [],
|
||||
})
|
||||
}
|
||||
// TODO: add crdt edits
|
||||
if (Math.random() < editsPerInterval && notes.length > 0) {
|
||||
const target = _.sample(notes)!
|
||||
if (!target) {
|
||||
throw Error("Target item not found")
|
||||
}
|
||||
const updated = { ...target, text: "EDIT " + counter, color: _.sample(NOTE_COLORS)?.color! }
|
||||
connection.send({
|
||||
ackId: "perf",
|
||||
events: [
|
||||
{
|
||||
action: "item.front",
|
||||
boardId,
|
||||
itemIds: [updated.id],
|
||||
},
|
||||
{
|
||||
action: "item.update",
|
||||
boardId,
|
||||
items: [updated],
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
}, interval)
|
||||
}
|
||||
if (event.action === "board.join.ack") {
|
||||
localEvents.push({ action: "nickname.set", nickname })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Environment variables.
|
||||
const USER_COUNT = parseInt(process.env.USER_COUNT ?? "10")
|
||||
const BOARD_ID = process.env.BOARD_ID
|
||||
if (!BOARD_ID) {
|
||||
throw Error("BOARD_ID missing. Please specify one more board ids separated by comma (,)")
|
||||
}
|
||||
const BOARD_IDS = BOARD_ID.split(",")
|
||||
const DOMAIN = process.env.DOMAIN
|
||||
|
||||
const NOTES_PER_SEC = parseFloat(process.env.NOTES_PER_SEC ?? "0.1")
|
||||
const TEXTS_PER_SEC = parseFloat(process.env.TEXTS_PER_SEC ?? "0.0")
|
||||
const EDITS_PER_SEC = parseFloat(process.env.EDITS_PER_SEC ?? "0")
|
||||
const CURSOR_MOVES_PER_SEC = parseFloat(process.env.CURSOR_MOVES_PER_SEC ?? "10")
|
||||
|
||||
// Calculated vars
|
||||
const interval = 1000 / CURSOR_MOVES_PER_SEC
|
||||
const notesPerInterval = (NOTES_PER_SEC / 1000) * interval
|
||||
const textsPerInterval = (TEXTS_PER_SEC / 1000) * interval
|
||||
const editsPerInterval = (EDITS_PER_SEC / 1000) * interval
|
||||
console.log(
|
||||
`Starting ${USER_COUNT} testers, moving cursors ${CURSOR_MOVES_PER_SEC}/sec, creating notes ${NOTES_PER_SEC}`,
|
||||
)
|
||||
console.log(`Total cursor events ${CURSOR_MOVES_PER_SEC * USER_COUNT}/s`)
|
||||
console.log(`Total creation events ${NOTES_PER_SEC * USER_COUNT}/s`)
|
||||
|
||||
for (let i = 0; i < USER_COUNT; i++) {
|
||||
createTester("perf-tester-" + (i + 1), BOARD_IDS[i % BOARD_IDS.length])
|
||||
}
|
||||
7
perf-tester/tsconfig.json
Normal file
7
perf-tester/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "../tsconfig",
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"outDir": "dist"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user