diff --git a/.gitignore b/.gitignore index 8ac5267c1ffc9b4a6bedbc26cca1d33b7ba2ab39..c8f936e58843319736767cdb33dab019671612e0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ - # Created by https://www.gitignore.io/api/vim,node,macos,visualstudiocode # Edit at https://www.gitignore.io/?templates=vim,node,macos,visualstudiocode @@ -12,6 +11,23 @@ src/signalbuddy src/yjs src/tiny-worker +# Temporary test dump files +dot-seq-add.json +dot-seq-erase.json +dot-seq-sync.json + +dot-par-add.json +dot-par-erase.json +dot-par-sync.json + +path-seq-add.json +path-seq-erase.json +path-seq-sync.json + +path-par-add.json +path-par-erase.json +path-par-sync.json + ### macOS ### # General .DS_Store diff --git a/__tests__/benchmark.test.js b/__tests__/benchmark.test.js index 9305a443d1a8d2215a33c0c81a635d68d97d8c0a..b05b4bb2d6f242983d8f2beebaecc5ea44af124b 100644 --- a/__tests__/benchmark.test.js +++ b/__tests__/benchmark.test.js @@ -1,3 +1,4 @@ +import fs from "fs" import chalk from "chalk" import { connect } from "../src/room.js" @@ -27,6 +28,38 @@ import { pathErase, } from "./benchmark.data.js" +// Adapted from https://github.com/jprichardson/buffer-json (MIT license) + +function stringify(value, space) { + return JSON.stringify(value, replacer, space) +} + +function parse(text) { + return JSON.parse(text, reviver) +} + +function replacer(key, value) { + if (value instanceof Uint8Array) { + return "base64:" + Buffer.from(value).toString("base64") + } + return value +} + +function reviver(key, value) { + if (typeof value == "string" && value.startsWith("base64:")) { + return Uint8Array.from(Buffer.from(value.slice("base64:".length), "base64")) + } + return value +} + +function dumpBSON(filename, data) { + return fs.writeFileSync(filename, stringify(data)) +} + +function loadBSON(filename) { + return parse(fs.readFileSync(filename)) +} + function printBenchmark(title, iterations, results) { process.stdout.write(`\n ${title} (${iterations} iterations):\n`) @@ -52,12 +85,12 @@ function printBenchmark(title, iterations, results) { process.stdout.write(`\n`) } -let room = null -let updateRoom = null -let syncRoom = null +//let room = null +//let updateRoom = null +//let syncRoom = null describe("drawing app mesh", () => { - beforeEach(async () => { + beforeEach(() => { getUserID.mockClear() getPeerHandle.mockClear() getPeerFootprint.mockClear() @@ -68,7 +101,7 @@ describe("drawing app mesh", () => { addEventListener.mockClear() MockConnection.mockClear() - room = await connect("room", MockConnection) + /*room = await connect("room", MockConnection) getEventListener( "room", "messageReceived", @@ -84,10 +117,10 @@ describe("drawing app mesh", () => { getEventListener( "sync", "messageReceived", - )(createMessageReceivedEvent(handshake, "tw-ml")) + )(createMessageReceivedEvent(handshake, "tw-ml"))*/ }) - afterEach(() => { + /*afterEach(() => { room.disconnect() room = null @@ -96,12 +129,12 @@ describe("drawing app mesh", () => { syncRoom.disconnect() syncRoom = null - }) + })*/ it("benchmarks a single draw and erase update sequentially", () => { const ITERATIONS = 1000 - jest.setTimeout(ITERATIONS * (10 + 300)) + jest.setTimeout(ITERATIONS * 400) const dotIDs = [] let prevTime @@ -109,13 +142,13 @@ describe("drawing app mesh", () => { let broadcasts = 0 let addLocTime = 0 - const addPackets = [] + let addPackets = [] let addSize = 0 let eraseLocTime = 0 - const erasePackets = [] + let erasePackets = [] let eraseSize = 0 let syncLocTime - const syncPackets = [] + let syncPackets = [] let syncSize = 0 let addRemTime = 0 @@ -127,43 +160,69 @@ describe("drawing app mesh", () => { let timeout - return new Promise((resolve) => { - broadcastListener.callback = (channel, message) => { - const currTime = process.hrtime() + let room + let updateRoom + let syncRoom + + return new Promise(async (resolve) => { + room = await connect("room", MockConnection) + getEventListener( + "room", + "messageReceived", + )(createMessageReceivedEvent(handshake, "tw-ml")) + return resolve() + }) + .then( + () => + new Promise((resolve) => { + broadcastListener.callback = (channel, message) => { + const currTime = process.hrtime() - broadcasts += 1 + broadcasts += 1 - if (broadcasts <= ITERATIONS) { - addLocTime += - (currTime[0] - prevTime[0]) * 1e9 + (currTime[1] - prevTime[1]) - addPackets.push(message) - addSize += message.message.length - } else { - eraseLocTime += - (currTime[0] - prevTime[0]) * 1e9 + (currTime[1] - prevTime[1]) - erasePackets.push(message) - eraseSize += message.message.length - } + if (broadcasts <= ITERATIONS) { + addLocTime += + (currTime[0] - prevTime[0]) * 1e9 + + (currTime[1] - prevTime[1]) + addPackets.push(message) + addSize += message.message.length + } else { + eraseLocTime += + (currTime[0] - prevTime[0]) * 1e9 + + (currTime[1] - prevTime[1]) + erasePackets.push(message) + eraseSize += message.message.length + } - prevTime = process.hrtime() + 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() - } - } + 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() + prevTime = process.hrtime() - dotIDs.push(room.addPath(dotDraw[0])) - }) + dotIDs.push(room.addPath(dotDraw[0])) + }), + ) + .then(() => { + broadcastListener.callback = null + + dumpBSON("dot-seq-add.json", addPackets) + addPackets = null + + dumpBSON("dot-seq-erase.json", erasePackets) + erasePackets = null + }) .then( () => new Promise((resolve) => { @@ -187,6 +246,30 @@ describe("drawing app mesh", () => { )(createMessageReceivedEvent(syncStep1)) }), ) + .then(() => { + sendListener.callback = null + + dumpBSON("dot-seq-sync.json", syncPackets) + syncPackets = null + + room.disconnect() + room = null + }) + .then( + () => + new Promise(async (resolve) => { + updateRoom = await connect("update", MockConnection) + getEventListener( + "update", + "messageReceived", + )(createMessageReceivedEvent(handshake, "tw-ml")) + + addPackets = loadBSON("dot-seq-add.json") + erasePackets = loadBSON("dot-seq-erase.json") + + return resolve() + }), + ) .then( () => new Promise((resolve) => { @@ -256,6 +339,27 @@ describe("drawing app mesh", () => { )(createMessageReceivedEvent(addPackets[0])) }), ) + .then(() => { + addPackets = null + erasePackets = null + + updateRoom.disconnect() + updateRoom = null + }) + .then( + () => + new Promise(async (resolve) => { + syncRoom = await connect("sync", MockConnection) + getEventListener( + "sync", + "messageReceived", + )(createMessageReceivedEvent(handshake, "tw-ml")) + + syncPackets = loadBSON("dot-seq-sync.json") + + return resolve() + }), + ) .then( () => new Promise((resolve) => { @@ -292,24 +396,34 @@ describe("drawing app mesh", () => { }), ) .then(() => { + syncPackets = null + + syncRoom.disconnect() + syncRoom = null + }) + .then(() => { + addPackets = loadBSON("dot-seq-add.json").length + erasePackets = loadBSON("dot-seq-erase.json").length + syncPackets = loadBSON("dot-seq-sync.json").length + printBenchmark("single draw and erase [sequential]", ITERATIONS, { addPath: { timeLoc: addLocTime, - packets: addPackets.length, + packets: addPackets, size: addSize, timeRem: addRemTime, events: addEvents, }, extendErasureIntervals: { timeLoc: eraseLocTime, - packets: erasePackets.length, + packets: erasePackets, size: eraseSize, timeRem: eraseRemTime, events: eraseEvents, }, synchronisation: { timeLoc: syncLocTime, - packets: syncPackets.length, + packets: syncPackets, size: syncSize, timeRem: syncRemTime, events: syncEvents, @@ -318,7 +432,7 @@ describe("drawing app mesh", () => { }) }) - it("benchmarks a single draw and erase update in parallel", () => { + /*it("benchmarks a single draw and erase update in parallel", () => { const ITERATIONS = 1000 jest.setTimeout(ITERATIONS * 20) @@ -1480,5 +1594,5 @@ describe("drawing app mesh", () => { resolve() }), ) - }) + })*/ })