-
Moritz Langenstein authoredMoritz Langenstein authored
benchmark.test.js 17.39 KiB
import chalk from "chalk"
import { connect } from "../src/room.js"
import MockConnection, {
getUserID,
getPeerHandle,
getPeerFootprint,
send,
sendListener,
broadcast,
broadcastListener,
terminatePeer,
destructor,
addEventListener,
eventListeners,
} from "../src/connection/MockConnection.js"
import {
handshake,
syncStep1,
syncDone,
dotDraw,
dotErase,
pathDraw,
pathErase,
} from "./benchmark.data.js"
function printBenchmark(title, iterations, results) {
process.stdout.write(`\n ${title} (${iterations} iterations):\n`)
for (const title in results) {
const { time, packets, size } = results[title]
const synchronisation = title == "synchronisation"
process.stdout.write(
chalk` {yellow ⧗} {dim ${title}:} {yellow.inverse ${(
time /
(1e6 * (synchronisation ? 1 : iterations))
).toFixed(3)}ms ${
synchronisation ? "total" : "/ it"
}} {magenta.inverse ${size}B} {dim in ${packets} packet(s)}\n`,
)
}
process.stdout.write(`\n`)
}
let room = null
describe("drawing app mesh", () => {
beforeEach(async () => {
getUserID.mockClear()
getPeerHandle.mockClear()
getPeerFootprint.mockClear()
send.mockClear()
broadcast.mockClear()
terminatePeer.mockClear()
destructor.mockClear()
addEventListener.mockClear()
MockConnection.mockClear()
room = await connect("data", MockConnection)
eventListeners.get("messageReceived")({ detail: handshake })
})
afterEach(() => {
room.disconnect()
room = null
})
it("benchmarks a single draw and erase update sequentially", () => {
const ITERATIONS = 1000
jest.setTimeout(ITERATIONS * 10)
const dotIDs = []
let prevTime
let broadcasts = 0
let addTime = 0
let addPackets = 0
let addSize = 0
let eraseTime = 0
let erasePackets = 0
let eraseSize = 0
let syncTime
let syncPackets = 0
let syncSize = 0
let timeout
return new Promise((resolve) => {
broadcastListener.callback = (channel, message) => {
const currTime = process.hrtime()
broadcasts += 1
if (broadcasts <= ITERATIONS) {
addTime +=
(currTime[0] - prevTime[0]) * 1e9 + (currTime[1] - prevTime[1])
addPackets += 1
addSize += message.message.length
} else {
eraseTime +=
(currTime[0] - prevTime[0]) * 1e9 + (currTime[1] - prevTime[1])
erasePackets += 1
eraseSize += message.message.length
}
prevTime = process.hrtime()
if (broadcasts < ITERATIONS) {
dotIDs.push(room.addPath(dotDraw[0]))
} else if (broadcasts < ITERATIONS * 2) {
room.extendErasureIntervals(
dotIDs[broadcasts - ITERATIONS],
dotErase[0][0],
dotErase[0][1],
)
} else {
resolve()
}
}
prevTime = process.hrtime()
dotIDs.push(room.addPath(dotDraw[0]))
}).then(
() =>
new Promise((resolve) => {
sendListener.callback = (uid, channel, message) => {
const currTime = process.hrtime()
syncTime =
(currTime[0] - prevTime[0]) * 1e9 + (currTime[1] - prevTime[1])
syncPackets += 1
syncSize += message.message.length
clearTimeout(timeout)
timeout = setTimeout(() => {
printBenchmark("single draw and erase [sequential]", ITERATIONS, {
addPath: { time: addTime, packets: addPackets, size: addSize },
extendErasureIntervals: {
time: eraseTime,
packets: erasePackets,
size: eraseSize,
},
synchronisation: {
time: syncTime,
packets: syncPackets,
size: syncSize,
},
})
resolve()
}, 1000)
}
prevTime = process.hrtime()
eventListeners.get("messageReceived")({ detail: syncStep1 })
}),
)
})
it("benchmarks a single draw and erase update in parallel", () => {
const ITERATIONS = 1000
jest.setTimeout(ITERATIONS * 10)
const dotIDs = []
let prevTime
let addTime
let addPackets = 0
let addSize = 0
let eraseTime
let erasePackets = 0
let eraseSize = 0
let syncTime
let syncPackets = 0
let syncSize = 0
let timeout
return new Promise((resolve) => {
broadcastListener.callback = (channel, message) => {
const currTime = process.hrtime()
addTime =
(currTime[0] - prevTime[0]) * 1e9 + (currTime[1] - prevTime[1])
addPackets += 1
addSize += message.message.length
clearTimeout(timeout)
timeout = setTimeout(() => resolve(), 1000)
}
prevTime = process.hrtime()
for (let i = 0; i < ITERATIONS; i++) {
dotIDs.push(room.addPath(dotDraw[0]))
}
})
.then(
() =>
new Promise((resolve) => {
broadcastListener.callback = (channel, message) => {
const currTime = process.hrtime()
eraseTime =
(currTime[0] - prevTime[0]) * 1e9 + (currTime[1] - prevTime[1])
erasePackets += 1
eraseSize += message.message.length
clearTimeout(timeout)
timeout = setTimeout(() => {
resolve()
}, 1000)
}
prevTime = process.hrtime()
for (let i = 0; i < ITERATIONS; i++) {
room.extendErasureIntervals(
dotIDs[i],
dotErase[0][0],
dotErase[0][1],
)
}
}),
)
.then(
() =>
new Promise((resolve) => {
sendListener.callback = (uid, channel, message) => {
const currTime = process.hrtime()
syncTime =
(currTime[0] - prevTime[0]) * 1e9 + (currTime[1] - prevTime[1])
syncPackets += 1
syncSize += message.message.length
clearTimeout(timeout)
timeout = setTimeout(() => {
printBenchmark("single draw and erase [parallel]", ITERATIONS, {
addPath: {
time: addTime,
packets: addPackets,
size: addSize,
},
extendErasureIntervals: {
time: eraseTime,
packets: erasePackets,
size: eraseSize,
},
synchronisation: {
time: syncTime,
packets: syncPackets,
size: syncSize,
},
})
resolve()
}, 1000)
}
prevTime = process.hrtime()
eventListeners.get("messageReceived")({ detail: syncStep1 })
}),
)
})
it("communicates a single draw and erase update", () => {
let dotID
let syncPackets = 0
let syncDonePacket = -1
let timeout
return new Promise((resolve) => {
broadcastListener.callback = (channel, message) => {
expect(channel).toEqual("y-js")
expect(message.message instanceof Uint8Array).toBe(true)
resolve()
}
dotID = room.addPath(dotDraw[0])
})
.then(
() =>
new Promise((resolve) => {
broadcastListener.callback = (channel, message) => {
expect(channel).toEqual("y-js")
expect(message.message instanceof Uint8Array).toBe(true)
resolve()
}
room.extendErasureIntervals(dotID, dotErase[0][0], dotErase[0][1])
}),
)
.then(
() =>
new Promise((resolve) => {
sendListener.callback = (uid, channel, message) => {
expect(uid).toEqual("tiger")
expect(channel).toEqual("y-js")
expect(message.message instanceof Uint8Array).toBe(true)
syncPackets += 1
if (
message.message.length == syncDone.message.message.length &&
JSON.stringify(Object.assign(message, { uuid: undefined })) ==
JSON.stringify(syncDone.message)
) {
expect(syncDonePacket).toEqual(-1)
syncDonePacket = syncPackets
}
clearTimeout(timeout)
timeout = setTimeout(() => {
expect(syncDonePacket).toEqual(syncPackets)
resolve()
}, 1000)
}
eventListeners.get("messageReceived")({ detail: syncStep1 })
}),
)
})
it("benchmarks a path draw and erase update sequentially", () => {
const ITERATIONS = 1000
jest.setTimeout(ITERATIONS * 30000)
const pathIDs = []
let prevTime
let currTime
let broadcasts = 0
let addTime = 0
let addPackets = 0
let addSize = 0
let eraseTime = 0
let erasePackets = 0
let eraseSize = 0
let syncTime
let syncPackets = 0
let syncSize = 0
let timeout
return new Promise((resolve) => {
broadcastListener.callback = (channel, message) => {
currTime = process.hrtime()
clearTimeout(timeout)
timeout = setTimeout(() => {
broadcasts += 1
if (broadcasts <= ITERATIONS) {
addTime +=
(currTime[0] - prevTime[0]) * 1e9 + (currTime[1] - prevTime[1])
addPackets += 1
addSize += message.message.length
} else {
eraseTime +=
(currTime[0] - prevTime[0]) * 1e9 + (currTime[1] - prevTime[1])
erasePackets += 1
eraseSize += message.message.length
}
prevTime = process.hrtime()
if (broadcasts < ITERATIONS) {
const tmpPathID = room.addPath(pathDraw[0])
pathIDs.push(tmpPathID)
for (let i = 1; i < pathDraw.lenth; i++) {
room.extendPath(tmpPathID, pathDraw[i])
}
} else if (broadcasts < ITERATIONS * 2) {
const tmpPathID = pathIDs[broadcasts - ITERATIONS]
for (let i = 0; i < pathErase.length; i++) {
room.extendErasureIntervals(
tmpPathID,
pathErase[i][0],
pathErase[i][1],
)
}
} else {
resolve()
}
}, 100)
}
prevTime = process.hrtime()
const tmpPathID = room.addPath(pathDraw[0])
pathIDs.push(tmpPathID)
for (let i = 1; i < pathDraw.lenth; i++) {
room.extendPath(tmpPathID, pathDraw[i])
}
}).then(
() =>
new Promise((resolve) => {
sendListener.callback = (uid, channel, message) => {
const currTime = process.hrtime()
syncTime =
(currTime[0] - prevTime[0]) * 1e9 + (currTime[1] - prevTime[1])
syncPackets += 1
syncSize += message.message.length
clearTimeout(timeout)
timeout = setTimeout(() => {
printBenchmark("path draw and erase [sequential]", ITERATIONS, {
addPath: { time: addTime, packets: addPackets, size: addSize },
extendErasureIntervals: {
time: eraseTime,
packets: erasePackets,
size: eraseSize,
},
synchronisation: {
time: syncTime,
packets: syncPackets,
size: syncSize,
},
})
resolve()
}, 1000)
}
prevTime = process.hrtime()
eventListeners.get("messageReceived")({ detail: syncStep1 })
}),
)
})
it("benchmarks a path draw and erase update in parallel", () => {
const ITERATIONS = 1000
const BLOCKSIZE = 10
jest.setTimeout(ITERATIONS * 200)
const pathIDs = []
let prevTime
let addTime
let addPackets = 0
let addSize = 0
let eraseTime
let erasePackets = 0
let eraseSize = 0
let syncTime
let syncPackets = 0
let syncSize = 0
let timeout
return new Promise((resolve) => {
broadcastListener.callback = (channel, message) => {
const currTime = process.hrtime()
addTime =
(currTime[0] - prevTime[0]) * 1e9 + (currTime[1] - prevTime[1])
addPackets += 1
addSize += message.message.length
clearTimeout(timeout)
timeout = setTimeout(() => resolve(), 1000)
}
prevTime = process.hrtime()
for (let j = 0; j < ITERATIONS; j++) {
const tmpPathID = room.addPath(pathDraw[0])
pathIDs.push(tmpPathID)
for (let i = 1; i < pathDraw.lenth; i++) {
room.extendPath(tmpPathID, pathDraw[i])
}
}
})
.then(
() =>
new Promise((resolve) => {
broadcastListener.callback = (channel, message) => {
const currTime = process.hrtime()
eraseTime =
(currTime[0] - prevTime[0]) * 1e9 + (currTime[1] - prevTime[1])
erasePackets += 1
eraseSize += message.message.length
clearTimeout(timeout)
timeout = setTimeout(() => {
resolve()
}, 10000)
}
// Necessary to allow yjs to execute transactions (majority of processing time)
function erasePath(sj) {
if (sj >= ITERATIONS) return
for (let j = sj; j < Math.min(sj + BLOCKSIZE, ITERATIONS); j++) {
const tmpPathID = pathIDs[j]
for (let i = 0; i < pathErase.length; i++) {
room.extendErasureIntervals(
tmpPathID,
pathErase[i][0],
pathErase[i][1],
)
}
}
setTimeout(erasePath, 0, sj + BLOCKSIZE)
}
prevTime = process.hrtime()
erasePath(0)
}),
)
.then(
() =>
new Promise((resolve) => {
sendListener.callback = (uid, channel, message) => {
const currTime = process.hrtime()
syncTime =
(currTime[0] - prevTime[0]) * 1e9 + (currTime[1] - prevTime[1])
syncPackets += 1
syncSize += message.message.length
clearTimeout(timeout)
timeout = setTimeout(() => {
printBenchmark("path draw and erase [parallel]", ITERATIONS, {
addPath: {
time: addTime,
packets: addPackets,
size: addSize,
},
extendErasureIntervals: {
time: eraseTime,
packets: erasePackets,
size: eraseSize,
},
synchronisation: {
time: syncTime,
packets: syncPackets,
size: syncSize,
},
})
resolve()
}, 1000)
}
prevTime = process.hrtime()
eventListeners.get("messageReceived")({ detail: syncStep1 })
}),
)
})
it("communicates a path draw and erase update", () => {
let pathID
let syncPackets = 0
let syncDonePacket = -1
let timeout
return new Promise((resolve) => {
broadcastListener.callback = (channel, message) => {
expect(channel).toEqual("y-js")
expect(message.message instanceof Uint8Array).toBe(true)
clearTimeout(timeout)
timeout = setTimeout(() => resolve(), 1000)
}
pathID = room.addPath(pathDraw[0])
for (let i = 1; i < pathDraw.lenth; i++) {
room.extendPath(pathID, pathDraw[i])
}
})
.then(
() =>
new Promise((resolve) => {
broadcastListener.callback = (channel, message) => {
expect(channel).toEqual("y-js")
expect(message.message instanceof Uint8Array).toBe(true)
clearTimeout(timeout)
timeout = setTimeout(() => resolve(), 1000)
}
for (let i = 0; i < pathErase.length; i++) {
room.extendErasureIntervals(
pathID,
pathErase[i][0],
pathErase[i][1],
)
}
}),
)
.then(
() =>
new Promise((resolve) => {
sendListener.callback = (uid, channel, message) => {
expect(uid).toEqual("tiger")
expect(channel).toEqual("y-js")
expect(message.message instanceof Uint8Array).toBe(true)
syncPackets += 1
if (
message.message.length == syncDone.message.message.length &&
JSON.stringify(Object.assign(message, { uuid: undefined })) ==
JSON.stringify(syncDone.message)
) {
expect(syncDonePacket).toEqual(-1)
syncDonePacket = syncPackets
}
clearTimeout(timeout)
timeout = setTimeout(() => {
expect(syncDonePacket).toEqual(syncPackets)
resolve()
}, 1000)
}
eventListeners.get("messageReceived")({ detail: syncStep1 })
}),
)
})
})