diff --git a/.gitignore b/.gitignore index 174b618b9ddb65d99bcce1488e24bc8bec4bd02e..66c5fc96b346fa69bfbbefce0b84adae5e9f1434 100644 --- a/.gitignore +++ b/.gitignore @@ -167,6 +167,7 @@ typings/ # react / gatsby (customised) public/js +public/benchmarks.html # vuepress build output .vuepress/dist diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 751523dc927761761ac93f02f576255b921ecb07..9a1558a8447288471a828f5cbe2b4c920fb7b8dd 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -117,7 +117,9 @@ benchmark: - master script: - apt-get -y install gnuplot - - npm run test-benchmark + - npm run build:bench + - cp __benchmarks__/benchmarks.html public/benchmarks.html + - npm run benchmarks - npm run plot artifacts: paths: diff --git a/__benchmarks__/benchmarks.html b/__benchmarks__/benchmarks.html new file mode 100644 index 0000000000000000000000000000000000000000..5a7b264ef1b61bd8c791bd09ed268d31ce1e6dd2 --- /dev/null +++ b/__benchmarks__/benchmarks.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + </head> + <body> + <script src="js/benchmarks.js"></script> + </body> +</html> diff --git a/__tests__/benchmark.test.js b/__benchmarks__/benchmarks.js similarity index 66% rename from __tests__/benchmark.test.js rename to __benchmarks__/benchmarks.js index 96617ef4036186c86964a82a120ebbddcfe9883d..7dd3befc821a6c8f2ae8da09198b0d1a4cf29c13 100644 --- a/__tests__/benchmark.test.js +++ b/__benchmarks__/benchmarks.js @@ -1,36 +1,94 @@ -import fs from "fs" +import { test, equal, ok, notOk } from "zora" + import chalk from "chalk" -import gc from "expose-gc/function" import { connect } from "../src/room.js" - +import WasmCRDT from "../src/wasm-crdt.js" import MockConnection, { - getUserID, - getPeerHandle, - getPeerFootprint, - send, + userID, sendListener, - broadcast, broadcastListener, - terminatePeer, - destructor, - addEventListener, getEventListener, } from "../src/connection/MockConnection.js" import { - createMessageReceivedEvent, + createMessageReceivedEvent as _createMessageReceivedEvent, handshake, - syncStep1, - syncDone, dotDraw, dotErase, pathDraw, pathErase, -} from "./benchmark.data.js" - -// Adapted from https://github.com/jprichardson/buffer-json (MIT license) +} from "./data.js" + +const remoteUserID = "392bf960-1d18-4482-bbbf-1c85e0132c9a" + +const createMessageReceivedEvent = (message, channel = "crdt") => + _createMessageReceivedEvent(message, channel, remoteUserID) + +const syncStep1 = { + uuid: "6e20b20d-e1d8-405d-8a61-d56cb1c47a24", + message: Uint8Array.of( + 130, + 164, + 116, + 121, + 112, + 101, + 171, + 115, + 121, + 110, + 99, + 32, + 115, + 116, + 101, + 112, + 32, + 49, + 167, + 109, + 101, + 115, + 115, + 97, + 103, + 101, + 196, + 3, + 0, + 0, + 0, + ), + slice: 0, + length: 1, + compressed: false, +} +const syncDone = { + message: Uint8Array.of( + 129, + 164, + 116, + 121, + 112, + 101, + 169, + 115, + 121, + 110, + 99, + 32, + 100, + 111, + 110, + 101, + ), + slice: 0, + length: 1, + compressed: false, +} +// Start: Adapted from https://github.com/jprichardson/buffer-json (MIT license) function stringify(value, space) { return JSON.stringify(value, replacer, space) } @@ -52,17 +110,73 @@ function reviver(key, value) { } return value } +// End: Adapted from https://github.com/jprichardson/buffer-json (MIT license) + +let fs -function dumpBSON(filename, data) { - return fs.writeFileSync(filename, stringify(data)) +async function initFileSystem() { + fs = await new Promise((resolve, reject) => + window.webkitRequestFileSystem( + window.TEMPORARY, + 1024 * 1024 * 100, + resolve, + reject, + ), + ) } -function loadBSON(filename) { - return parse(fs.readFileSync(filename)) +async function dumpBSON(filename, data) { + await new Promise((resolve) => { + fs.root.getFile( + filename, + { create: false }, + (fileEntry) => { + fileEntry.remove(resolve, console.error) + }, + resolve, + ) + }) + + await new Promise((resolve) => { + fs.root.getFile( + filename, + { create: true, exclusive: true }, + (fileEntry) => { + fileEntry.createWriter((fileWriter) => { + fileWriter.onwriteend = resolve + fileWriter.onerror = console.error + + const blob = new Blob([stringify(data)], { type: "text/plain" }) + + fileWriter.write(blob) + }, console.error) + }, + console.error, + ) + }) +} + +async function loadBSON(filename) { + return await new Promise((resolve) => { + fs.root.getFile( + filename, + {}, + (fileEntry) => { + fileEntry.file((file) => { + const reader = new FileReader() + + reader.onloadend = () => resolve(parse(reader.result)) + + reader.readAsText(file) + }, console.error) + }, + console.error, + ) + }) } function printBenchmark(title, iterations, results) { - process.stdout.write(`\n ${title} (${iterations} iterations):\n`) + console.debug(`\n ${title} (${iterations} iterations):\n`) for (const title in results) { const { @@ -76,10 +190,10 @@ function printBenchmark(title, iterations, results) { } = results[title] const synchronisation = title == "synchronisation" - process.stdout.write( + console.debug( chalk` {yellow ⧗} {dim ${title}:} {yellow.inverse ${( timeLoc / - (1e6 * (synchronisation ? 1 : iterations)) + (1e3 * (synchronisation ? 1 : iterations)) ).toFixed(3)}ms ${synchronisation ? "total" : "/ it"}} + {red.inverse ${( encodeRAM / (1024 * 1024) @@ -87,7 +201,7 @@ function printBenchmark(title, iterations, results) { 3, )}MB} => {dim ${packets} packet(s)} => {magenta.inverse ${size}B} => {yellow.inverse ${( timeRem / - (1e6 * (synchronisation ? 1 : iterations)) + (1e3 * (synchronisation ? 1 : iterations)) ).toFixed(3)}ms ${synchronisation ? "total" : "/ it"}} + {red.inverse ${( decodeRAM / (1024 * 1024) @@ -95,60 +209,23 @@ function printBenchmark(title, iterations, results) { ) } - process.stdout.write(`\n`) + console.debug(`\n`) } function writeBenchmarkHeader(filename, title, results) { - const columns = ["iterations"] - - for (const title of results) { - columns.push(`${title}_timeLoc`) - columns.push(`${title}_encodeRAM`) - columns.push(`${title}_packets`) - columns.push(`${title}_size`) - columns.push(`${title}_timeRem`) - columns.push(`${title}_decodeRAM`) - columns.push(`${title}_events`) - } - - return fs.writeFileSync( - filename, - `# Benchmark: ${title}\n# ${columns.join("\t")}\n`, - ) + console.info(JSON.stringify({ filename, title, results })) } function appendBenchmark(filename, iterations, results) { - const columns = [iterations] - - for (const title in results) { - const { - timeLoc, - encodeRAM, - packets, - size, - timeRem, - decodeRAM, - events, - } = results[title] - - columns.push(timeLoc) - columns.push(encodeRAM) - columns.push(packets) - columns.push(size) - columns.push(timeRem) - columns.push(decodeRAM) - columns.push(events) - } - - return fs.appendFileSync(filename, `${columns.join("\t")}\n`) + console.info(JSON.stringify({ filename, iterations, results })) } function captureHeapUsage() { for (let i = 0; i < 10; i++) { - gc() + window.gc() } - return process.memoryUsage().heapUsed + return performance.memory.usedJSHeapSize } function runBidirectionalBenchmark( @@ -248,7 +325,9 @@ function runBidirectionalBenchmark( return ( // eslint-disable-next-line no-async-promise-executor new Promise(async (resolve) => { - room = await connect("room", MockConnection) + userID.uuid = "61a540d6-4522-48c7-a660-1ed501503cb7" + + room = await connect("room", WasmCRDT, MockConnection) getEventListener( "room", "messageReceived", @@ -264,10 +343,10 @@ function runBidirectionalBenchmark( let broadcasts = 0 broadcastListener.callback = (channel, message) => { - currTime = process.hrtime() + currTime = window.performance.now() - expect(channel).toEqual("y-js") - expect(message.message instanceof Uint8Array).toBe(true) + equal(channel, "crdt") + ok(message.message instanceof Uint8Array) addPackets[addPackets.length - 1].push(message) addSize += message.message.length @@ -276,11 +355,9 @@ function runBidirectionalBenchmark( timeout = setTimeout(() => { broadcasts += 1 - addLocTime += - (currTime[0] - prevTime[0]) * 1e9 + - (currTime[1] - prevTime[1]) + addLocTime += (currTime - prevTime) * 1e3 - prevTime = process.hrtime() + prevTime = window.performance.now() addOnBroadcastGroup( room, @@ -294,7 +371,7 @@ function runBidirectionalBenchmark( }, addBroadcastGroupTimeout) } - prevTime = process.hrtime() + prevTime = window.performance.now() addOnInitFrontend( room, @@ -306,10 +383,10 @@ function runBidirectionalBenchmark( ) }), ) - .then(() => { + .then(async () => { broadcastListener.callback = null - dumpBSON(addPacketsFilename, addPackets) + await dumpBSON(addPacketsFilename, addPackets) addPackets = null addRAM = captureHeapUsage() @@ -320,10 +397,10 @@ function runBidirectionalBenchmark( let broadcasts = 0 broadcastListener.callback = (channel, message) => { - currTime = process.hrtime() + currTime = window.performance.now() - expect(channel).toEqual("y-js") - expect(message.message instanceof Uint8Array).toBe(true) + equal(channel, "crdt") + ok(message.message instanceof Uint8Array) erasePackets[erasePackets.length - 1].push(message) eraseSize += message.message.length @@ -332,11 +409,9 @@ function runBidirectionalBenchmark( timeout = setTimeout(() => { broadcasts += 1 - eraseLocTime += - (currTime[0] - prevTime[0]) * 1e9 + - (currTime[1] - prevTime[1]) + eraseLocTime += (currTime - prevTime) * 1e3 - prevTime = process.hrtime() + prevTime = window.performance.now() eraseOnBroadcastGroup( room, @@ -350,7 +425,7 @@ function runBidirectionalBenchmark( }, eraseBroadcastGroupTimeout) } - prevTime = process.hrtime() + prevTime = window.performance.now() eraseOnInitFrontend( room, @@ -362,10 +437,10 @@ function runBidirectionalBenchmark( ) }), ) - .then(() => { + .then(async () => { broadcastListener.callback = null - dumpBSON(erasePacketsFilename, erasePackets) + await dumpBSON(erasePacketsFilename, erasePackets) erasePackets = null eraseRAM = captureHeapUsage() @@ -374,15 +449,13 @@ function runBidirectionalBenchmark( () => new Promise((resolve) => { sendListener.callback = (uid, channel, message) => { - const currTime = process.hrtime() + const currTime = window.performance.now() - expect(uid).toEqual("moritz") - expect(channel).toEqual("y-js") - expect(message.message instanceof Uint8Array).toBe(true) + equal(uid, remoteUserID) + equal(channel, "crdt") + ok(message.message instanceof Uint8Array) - syncLocTime = - (currTime[0] - prevTime[0]) * 1e9 + - (currTime[1] - prevTime[1]) + syncLocTime = (currTime - prevTime) * 1e3 syncPackets.push(message) syncSize += message.message.length @@ -394,7 +467,7 @@ function runBidirectionalBenchmark( ) } - prevTime = process.hrtime() + prevTime = window.performance.now() getEventListener( "room", @@ -402,10 +475,10 @@ function runBidirectionalBenchmark( )(createMessageReceivedEvent(syncStep1)) }), ) - .then(() => { + .then(async () => { sendListener.callback = null - dumpBSON(syncPacketsFilename, syncPackets) + await dumpBSON(syncPacketsFilename, syncPackets) syncPackets = null syncRAM = captureHeapUsage() @@ -419,7 +492,9 @@ function runBidirectionalBenchmark( () => // eslint-disable-next-line no-async-promise-executor new Promise(async (resolve) => { - updateRoom = await connect("update", MockConnection) + userID.uuid = "5c9e550b-3de8-4a32-80e1-80c08c19891a" + + updateRoom = await connect("update", WasmCRDT, MockConnection) getEventListener( "update", "messageReceived", @@ -427,7 +502,7 @@ function runBidirectionalBenchmark( connectUpdRAM = captureHeapUsage() - addPackets = loadBSON(addPacketsFilename) + addPackets = await loadBSON(addPacketsFilename) return resolve() }), @@ -442,11 +517,9 @@ function runBidirectionalBenchmark( const timeoutCallback = () => { broadcasts += 1 - addRemTime += - (currTime[0] - prevTime[0]) * 1e9 + - (currTime[1] - prevTime[1]) + addRemTime += (currTime - prevTime) * 1e3 - prevTime = process.hrtime() + prevTime = window.performance.now() addOnEventGroup( addPackets, @@ -460,7 +533,7 @@ function runBidirectionalBenchmark( } addEventListener = (event) => { - currTime = process.hrtime() + currTime = window.performance.now() addEvents.push(addEventsCache ? { add: event } : null) @@ -468,7 +541,7 @@ function runBidirectionalBenchmark( timeout = setTimeout(timeoutCallback, addEventGroupTimeout) } eraseEventListener = (event) => { - currTime = process.hrtime() + currTime = window.performance.now() addEvents.push(addEventsCache ? { erase: event } : null) @@ -485,12 +558,12 @@ function runBidirectionalBenchmark( eraseEventListener, ) - prevTime = process.hrtime() + prevTime = window.performance.now() addOnInitBackend(addPackets, BLOCKSIZE) }), ) - .then(() => { + .then(async () => { updateRoom.removeEventListener( "addOrUpdatePath", addEventListener, @@ -505,12 +578,12 @@ function runBidirectionalBenchmark( addPackets = null - dumpBSON(addEventsFilename, addEvents) + await dumpBSON(addEventsFilename, addEvents) addEvents = null addRemRAM = captureHeapUsage() - erasePackets = loadBSON(erasePacketsFilename) + erasePackets = await loadBSON(erasePacketsFilename) }) .then( () => @@ -522,11 +595,9 @@ function runBidirectionalBenchmark( const timeoutCallback = () => { broadcasts += 1 - eraseRemTime += - (currTime[0] - prevTime[0]) * 1e9 + - (currTime[1] - prevTime[1]) + eraseRemTime += (currTime - prevTime) * 1e3 - prevTime = process.hrtime() + prevTime = window.performance.now() eraseOnEventGroup( erasePackets, @@ -541,7 +612,7 @@ function runBidirectionalBenchmark( } eraseEventListener = (event) => { - currTime = process.hrtime() + currTime = window.performance.now() eraseEvents.push(eraseEventsCache ? { erase: event } : null) @@ -557,12 +628,12 @@ function runBidirectionalBenchmark( eraseEventListener, ) - prevTime = process.hrtime() + prevTime = window.performance.now() eraseOnInitBackend(erasePackets, BLOCKSIZE) }), ) - .then(() => { + .then(async () => { updateRoom.removeEventListener( "removedIntervalsChange", eraseEventListener, @@ -571,7 +642,7 @@ function runBidirectionalBenchmark( erasePackets = null - dumpBSON(eraseEventsFilename, eraseEvents) + await dumpBSON(eraseEventsFilename, eraseEvents) eraseEvents = null eraseRemRAM = captureHeapUsage() @@ -585,7 +656,9 @@ function runBidirectionalBenchmark( () => // eslint-disable-next-line no-async-promise-executor new Promise(async (resolve) => { - syncRoom = await connect("sync", MockConnection) + userID.uuid = "a2108f84-3785-4696-8dd5-fb89b38d4f7f" + + syncRoom = await connect("sync", WasmCRDT, MockConnection) getEventListener( "sync", "messageReceived", @@ -593,7 +666,7 @@ function runBidirectionalBenchmark( connectSyncRAM = captureHeapUsage() - syncPackets = loadBSON(syncPacketsFilename) + syncPackets = await loadBSON(syncPacketsFilename) return resolve() }), @@ -602,11 +675,9 @@ function runBidirectionalBenchmark( () => new Promise((resolve) => { addEventListener = (event) => { - const currTime = process.hrtime() + const currTime = window.performance.now() - syncRemTime = - (currTime[0] - prevTime[0]) * 1e9 + - (currTime[1] - prevTime[1]) + syncRemTime = (currTime - prevTime) * 1e3 syncEvents.push(syncEventsCache ? { add: event } : null) @@ -624,11 +695,9 @@ function runBidirectionalBenchmark( ) } eraseEventListener = (event) => { - const currTime = process.hrtime() + const currTime = window.performance.now() - syncRemTime = - (currTime[0] - prevTime[0]) * 1e9 + - (currTime[1] - prevTime[1]) + syncRemTime = (currTime - prevTime) * 1e3 syncEvents.push(syncEventsCache ? { erase: event } : null) @@ -652,7 +721,7 @@ function runBidirectionalBenchmark( eraseEventListener, ) - prevTime = process.hrtime() + prevTime = window.performance.now() for (const syncPacket of syncPackets) { getEventListener( @@ -662,7 +731,7 @@ function runBidirectionalBenchmark( } }), ) - .then(() => { + .then(async () => { syncRoom.removeEventListener("addOrUpdatePath", addEventListener) addEventListener = null @@ -674,7 +743,7 @@ function runBidirectionalBenchmark( syncPackets = null - dumpBSON(syncEventsFilename, syncEvents) + await dumpBSON(syncEventsFilename, syncEvents) syncEvents = null syncRemRAM = captureHeapUsage() @@ -684,24 +753,24 @@ function runBidirectionalBenchmark( disconnectSyncRAM = captureHeapUsage() }) - .then(() => { + .then(async () => { if (!BENCHMARK) { return } - addPackets = loadBSON(addPacketsFilename).reduce( + addPackets = (await loadBSON(addPacketsFilename)).reduce( (sum, packets) => sum + packets.length, 0, ) - erasePackets = loadBSON(erasePacketsFilename).reduce( + erasePackets = (await loadBSON(erasePacketsFilename)).reduce( (sum, packets) => sum + packets.length, 0, ) - syncPackets = loadBSON(syncPacketsFilename).length + syncPackets = (await loadBSON(syncPacketsFilename)).length - addEvents = loadBSON(addEventsFilename).length - eraseEvents = loadBSON(eraseEventsFilename).length - syncEvents = loadBSON(syncEventsFilename).length + addEvents = (await loadBSON(addEventsFilename)).length + eraseEvents = (await loadBSON(eraseEventsFilename)).length + syncEvents = (await loadBSON(syncEventsFilename)).length const results = { addPath: { @@ -753,11 +822,13 @@ function addOnInitFrontendSequential( addPackets.push([]) const drawPathID = room.addPath(addData[0]) - pathIDs.push(drawPathID) + for (let i = 1; i < addData.length; i++) { room.extendPath(drawPathID, addData[i]) } + + room.endPath(drawPathID) } function addOnBroadcastGroupSequential( @@ -778,6 +849,8 @@ function addOnBroadcastGroupSequential( for (let i = 1; i < addData.length; i++) { room.extendPath(drawPathID, addData[i]) } + + room.endPath(drawPathID) } else { resolve() } @@ -919,11 +992,13 @@ function addOnInitFrontendParallel( for (let j = sj; j < Math.min(sj + BLOCKSIZE, ITERATIONS); j++) { const drawPathID = room.addPath(addData[0]) - pathIDs.push(drawPathID) + for (let i = 1; i < addData.length; i++) { room.extendPath(drawPathID, addData[i]) } + + room.endPath(drawPathID) } setTimeout(addPath, 0, sj + BLOCKSIZE) @@ -1060,13 +1135,13 @@ function syncOnSendGroupVerify(syncPackets, resolve) { JSON.stringify(Object.assign({}, packet, { uuid: undefined })) == JSON.stringify(syncDone) ) { - expect(syncDonePacket).toEqual(-1) + equal(syncDonePacket, -1) syncDonePacket = i } }) - expect(syncDonePacket).toEqual(syncPackets.length - 1) + equal(syncDonePacket, syncPackets.length - 1) resolve() } @@ -1084,7 +1159,7 @@ function addOnEventGroupVerify( const updateIntervals = {} for (const event of addEvents) { - expect(!event.add + !event.erase).toBe(1) + equal(!event.add + !event.erase, 1) if (event.add) { const { @@ -1101,8 +1176,8 @@ function addOnEventGroupVerify( } } - expect(updatePaths).toStrictEqual({ [pathIDs[0]]: addData }) - expect(updateIntervals).toStrictEqual({ [pathIDs[0]]: {} }) + equal(updatePaths, { [pathIDs[0]]: addData }) + equal(updateIntervals, { [pathIDs[0]]: {} }) resolve() } @@ -1120,8 +1195,8 @@ function eraseOnEventGroupVerify( const updateIntervals = {} for (const event of eraseEvents) { - expect(!event.add).toBe(true) - expect(!event.erase).toBe(false) + ok(!event.add) + notOk(!event.erase) const { detail: { id, intervals }, @@ -1130,7 +1205,7 @@ function eraseOnEventGroupVerify( updateIntervals[id] = intervals } - expect(updateIntervals).toStrictEqual({ + equal(updateIntervals, { [pathIDs[0]]: Object.assign( {}, new Array(addData.length).fill([[0, 0 + (addData.length > 1)]]), @@ -1151,7 +1226,7 @@ function syncOnEventGroupVerify( const syncIntervals = {} for (const event of syncEvents) { - expect(!event.add + !event.erase).toBe(1) + equal(!event.add + !event.erase, 1) if (event.add) { const { @@ -1168,8 +1243,8 @@ function syncOnEventGroupVerify( } } - expect(syncPaths).toStrictEqual({ [pathIDs[0]]: addData }) - expect(syncIntervals).toStrictEqual({ + equal(syncPaths, { [pathIDs[0]]: addData }) + equal(syncIntervals, { [pathIDs[0]]: Object.assign( {}, new Array(addData.length).fill([[0, 0 + (addData.length > 1)]]), @@ -1179,171 +1254,169 @@ function syncOnEventGroupVerify( resolve() } -describe("drawing app mesh", () => { - beforeEach(() => { - getUserID.mockClear() - getPeerHandle.mockClear() - getPeerFootprint.mockClear() - send.mockClear() - broadcast.mockClear() - terminatePeer.mockClear() - destructor.mockClear() - addEventListener.mockClear() - MockConnection.mockClear() - - captureHeapUsage() - }) +test("benchmark", async (t) => { + await initFileSystem() const ITERATIONSLIST = [10, 25, 50, 75, 100, 250, 500] - const BLOCKSIZE = 10 - - jest.setTimeout(1000 * 60 * 60) - - it("benchmarks a dot draw and erase update sequentially", () => { - return runBidirectionalBenchmark( - "dot draw and erase [sequential]" /* BENCHMARK */, - "plots/dot-seq-benchmark.tsv" /* FILENAME */, - ITERATIONSLIST /* ITERATIONSLIST */, - BLOCKSIZE /* BLOCKSIZE */, - dotDraw /* addData */, - dotErase /* eraseData */, - addOnInitFrontendSequential /* addOnInitFrontend */, - 100 /* addBroadcastGroupTimeout */, - addOnBroadcastGroupSequential /* addOnBroadcastGroup */, - ".dot-seq-add-packets.json" /* addPacketsFilename */, - eraseOnInitFrontendSequential /* eraseOnInitFrontend */, - 100 /* eraseBroadcastGroupTimeout */, - eraseOnBroadcastGroupSequential /* eraseOnBroadcastGroup */, - ".dot-seq-erase-packets.json" /* erasePacketsFilename */, - 1000 /* syncSendGroupTimeout */, - syncOnSendGroup /* syncOnSendGroup */, - ".dot-seq-sync-packets.json" /* syncPacketsFilename */, - addOnInitBackendSequential /* addOnInitBackend */, - 100 /* addEventGroupTimeout */, - addOnEventGroupSequential /* addOnEventGroup */, - false /* addEventsCache */, - ".dot-seq-add-events.json" /* addEventsFilename */, - eraseOnInitBackendSequential /* eraseOnInitBackend */, - 100 /* eraseEventGroupTimeout */, - eraseOnEventGroupSequential /* eraseOnEventGroupTimeout */, - false /* eraseEventsCache */, - ".dot-seq-erase-events.json" /* eraseEventsFilename */, - 1000 /* syncEventGroupTimeout */, - syncOnEventGroup /* syncOnEventGroup */, - false /* syncEventsCache */, - ".dot-seq-sync-events.json" /* syncEventsFilename */, - ) - }) + const BLOCKSIZE = 1000 //10 + + await t.test( + "benchmarks a dot draw and erase update sequentially", + async (/*t*/) => { + return runBidirectionalBenchmark( + "dot draw and erase [sequential]" /* BENCHMARK */, + "plots/dot-seq-benchmark.tsv" /* FILENAME */, + ITERATIONSLIST /* ITERATIONSLIST */, + BLOCKSIZE /* BLOCKSIZE */, + dotDraw /* addData */, + dotErase /* eraseData */, + addOnInitFrontendSequential /* addOnInitFrontend */, + 100 /* addBroadcastGroupTimeout */, + addOnBroadcastGroupSequential /* addOnBroadcastGroup */, + ".dot-seq-add-packets.json" /* addPacketsFilename */, + eraseOnInitFrontendSequential /* eraseOnInitFrontend */, + 100 /* eraseBroadcastGroupTimeout */, + eraseOnBroadcastGroupSequential /* eraseOnBroadcastGroup */, + ".dot-seq-erase-packets.json" /* erasePacketsFilename */, + 1000 /* syncSendGroupTimeout */, + syncOnSendGroup /* syncOnSendGroup */, + ".dot-seq-sync-packets.json" /* syncPacketsFilename */, + addOnInitBackendSequential /* addOnInitBackend */, + 100 /* addEventGroupTimeout */, + addOnEventGroupSequential /* addOnEventGroup */, + false /* addEventsCache */, + ".dot-seq-add-events.json" /* addEventsFilename */, + eraseOnInitBackendSequential /* eraseOnInitBackend */, + 100 /* eraseEventGroupTimeout */, + eraseOnEventGroupSequential /* eraseOnEventGroupTimeout */, + false /* eraseEventsCache */, + ".dot-seq-erase-events.json" /* eraseEventsFilename */, + 1000 /* syncEventGroupTimeout */, + syncOnEventGroup /* syncOnEventGroup */, + false /* syncEventsCache */, + ".dot-seq-sync-events.json" /* syncEventsFilename */, + ) + }, + ) - it("benchmarks a dot draw and erase update in parallel", () => { - return runBidirectionalBenchmark( - "dot draw and erase [parallel]" /* BENCHMARK */, - "plots/dot-par-benchmark.tsv" /* FILENAME */, - ITERATIONSLIST /* ITERATIONSLIST */, - BLOCKSIZE /* BLOCKSIZE */, - dotDraw /* addData */, - dotErase /* eraseData */, - addOnInitFrontendParallel /* addOnInitFrontend */, - 1000 /* addBroadcastGroupTimeout */, - addOnBroadcastGroupParallel /* addOnBroadcastGroup */, - ".dot-par-add-packets.json" /* addPacketsFilename */, - eraseOnInitFrontendParallel /* eraseOnInitFrontend */, - 1000 /* eraseBroadcastGroupTimeout */, - eraseOnBroadcastGroupParallel /* eraseOnBroadcastGroup */, - ".dot-par-erase-packets.json" /* erasePacketsFilename */, - 1000 /* syncSendGroupTimeout */, - syncOnSendGroup /* syncOnSendGroup */, - ".dot-par-sync-packets.json" /* syncPacketsFilename */, - addOnInitBackendParallel /* addOnInitBackend */, - 1000 /* addEventGroupTimeout */, - addOnEventGroupParallel /* addOnEventGroup */, - false /* addEventsCache */, - ".dot-par-add-events.json" /* addEventsFilename */, - eraseOnInitBackendParallel /* eraseOnInitBackend */, - 1000 /* eraseEventGroupTimeout */, - eraseOnEventGroupParallel /* eraseOnEventGroupTimeout */, - false /* eraseEventsCache */, - ".dot-par-erase-events.json" /* eraseEventsFilename */, - 1000 /* syncEventGroupTimeout */, - syncOnEventGroup /* syncOnEventGroup */, - false /* syncEventsCache */, - ".dot-par-sync-events.json" /* syncEventsFilename */, - ) - }) + await t.test( + "benchmarks a dot draw and erase update in parallel", + async (/*t*/) => { + return runBidirectionalBenchmark( + "dot draw and erase [parallel]" /* BENCHMARK */, + "plots/dot-par-benchmark.tsv" /* FILENAME */, + ITERATIONSLIST /* ITERATIONSLIST */, + BLOCKSIZE /* BLOCKSIZE */, + dotDraw /* addData */, + dotErase /* eraseData */, + addOnInitFrontendParallel /* addOnInitFrontend */, + 1000 /* addBroadcastGroupTimeout */, + addOnBroadcastGroupParallel /* addOnBroadcastGroup */, + ".dot-par-add-packets.json" /* addPacketsFilename */, + eraseOnInitFrontendParallel /* eraseOnInitFrontend */, + 1000 /* eraseBroadcastGroupTimeout */, + eraseOnBroadcastGroupParallel /* eraseOnBroadcastGroup */, + ".dot-par-erase-packets.json" /* erasePacketsFilename */, + 1000 /* syncSendGroupTimeout */, + syncOnSendGroup /* syncOnSendGroup */, + ".dot-par-sync-packets.json" /* syncPacketsFilename */, + addOnInitBackendParallel /* addOnInitBackend */, + 1000 /* addEventGroupTimeout */, + addOnEventGroupParallel /* addOnEventGroup */, + false /* addEventsCache */, + ".dot-par-add-events.json" /* addEventsFilename */, + eraseOnInitBackendParallel /* eraseOnInitBackend */, + 1000 /* eraseEventGroupTimeout */, + eraseOnEventGroupParallel /* eraseOnEventGroupTimeout */, + false /* eraseEventsCache */, + ".dot-par-erase-events.json" /* eraseEventsFilename */, + 1000 /* syncEventGroupTimeout */, + syncOnEventGroup /* syncOnEventGroup */, + false /* syncEventsCache */, + ".dot-par-sync-events.json" /* syncEventsFilename */, + ) + }, + ) - it("benchmarks a path draw and erase update sequentially", () => { - return runBidirectionalBenchmark( - "path draw and erase [sequential]" /* BENCHMARK */, - "plots/path-seq-benchmark.tsv" /* FILENAME */, - ITERATIONSLIST /* ITERATIONSLIST */, - BLOCKSIZE /* BLOCKSIZE */, - pathDraw /* addData */, - pathErase /* eraseData */, - addOnInitFrontendSequential /* addOnInitFrontend */, - 100 /* addBroadcastGroupTimeout */, - addOnBroadcastGroupSequential /* addOnBroadcastGroup */, - ".path-seq-add-packets.json" /* addPacketsFilename */, - eraseOnInitFrontendSequential /* eraseOnInitFrontend */, - 100 /* eraseBroadcastGroupTimeout */, - eraseOnBroadcastGroupSequential /* eraseOnBroadcastGroup */, - ".path-seq-erase-packets.json" /* erasePacketsFilename */, - 1000 /* syncSendGroupTimeout */, - syncOnSendGroup /* syncOnSendGroup */, - ".path-seq-sync-packets.json" /* syncPacketsFilename */, - addOnInitBackendSequential /* addOnInitBackend */, - 100 /* addEventGroupTimeout */, - addOnEventGroupSequential /* addOnEventGroup */, - false /* addEventsCache */, - ".path-seq-add-events.json" /* addEventsFilename */, - eraseOnInitBackendSequential /* eraseOnInitBackend */, - 100 /* eraseEventGroupTimeout */, - eraseOnEventGroupSequential /* eraseOnEventGroupTimeout */, - false /* eraseEventsCache */, - ".path-seq-erase-events.json" /* eraseEventsFilename */, - 1000 /* syncEventGroupTimeout */, - syncOnEventGroup /* syncOnEventGroup */, - false /* syncEventsCache */, - ".path-seq-sync-events.json" /* syncEventsFilename */, - ) - }) + await t.test( + "benchmarks a path draw and erase update sequentially", + async (/*t*/) => { + return runBidirectionalBenchmark( + "path draw and erase [sequential]" /* BENCHMARK */, + "plots/path-seq-benchmark.tsv" /* FILENAME */, + ITERATIONSLIST /* ITERATIONSLIST */, + BLOCKSIZE /* BLOCKSIZE */, + pathDraw /* addData */, + pathErase /* eraseData */, + addOnInitFrontendSequential /* addOnInitFrontend */, + 100 /* addBroadcastGroupTimeout */, + addOnBroadcastGroupSequential /* addOnBroadcastGroup */, + ".path-seq-add-packets.json" /* addPacketsFilename */, + eraseOnInitFrontendSequential /* eraseOnInitFrontend */, + 100 /* eraseBroadcastGroupTimeout */, + eraseOnBroadcastGroupSequential /* eraseOnBroadcastGroup */, + ".path-seq-erase-packets.json" /* erasePacketsFilename */, + 1000 /* syncSendGroupTimeout */, + syncOnSendGroup /* syncOnSendGroup */, + ".path-seq-sync-packets.json" /* syncPacketsFilename */, + addOnInitBackendSequential /* addOnInitBackend */, + 100 /* addEventGroupTimeout */, + addOnEventGroupSequential /* addOnEventGroup */, + false /* addEventsCache */, + ".path-seq-add-events.json" /* addEventsFilename */, + eraseOnInitBackendSequential /* eraseOnInitBackend */, + 100 /* eraseEventGroupTimeout */, + eraseOnEventGroupSequential /* eraseOnEventGroupTimeout */, + false /* eraseEventsCache */, + ".path-seq-erase-events.json" /* eraseEventsFilename */, + 1000 /* syncEventGroupTimeout */, + syncOnEventGroup /* syncOnEventGroup */, + false /* syncEventsCache */, + ".path-seq-sync-events.json" /* syncEventsFilename */, + ) + }, + ) - it("benchmarks a path draw and erase update in parallel", () => { - return runBidirectionalBenchmark( - "path draw and erase [parallel]" /* BENCHMARK */, - "plots/path-par-benchmark.tsv" /* FILENAME */, - ITERATIONSLIST /* ITERATIONSLIST */, - BLOCKSIZE /* BLOCKSIZE */, - pathDraw /* addData */, - pathErase /* eraseData */, - addOnInitFrontendParallel /* addOnInitFrontend */, - 5000 /* addBroadcastGroupTimeout */, - addOnBroadcastGroupParallel /* addOnBroadcastGroup */, - ".path-par-add-packets.json" /* addPacketsFilename */, - eraseOnInitFrontendParallel /* eraseOnInitFrontend */, - 5000 /* eraseBroadcastGroupTimeout */, - eraseOnBroadcastGroupParallel /* eraseOnBroadcastGroup */, - ".path-par-erase-packets.json" /* erasePacketsFilename */, - 5000 /* syncSendGroupTimeout */, - syncOnSendGroup /* syncOnSendGroup */, - ".path-par-sync-packets.json" /* syncPacketsFilename */, - addOnInitBackendParallel /* addOnInitBackend */, - 5000 /* addEventGroupTimeout */, - addOnEventGroupParallel /* addOnEventGroup */, - false /* addEventsCache */, - ".path-par-add-events.json" /* addEventsFilename */, - eraseOnInitBackendParallel /* eraseOnInitBackend */, - 5000 /* eraseEventGroupTimeout */, - eraseOnEventGroupParallel /* eraseOnEventGroupTimeout */, - false /* eraseEventsCache */, - ".path-par-erase-events.json" /* eraseEventsFilename */, - 5000 /* syncEventGroupTimeout */, - syncOnEventGroup /* syncOnEventGroup */, - false /* syncEventsCache */, - ".path-par-sync-events.json" /* syncEventsFilename */, - ) - }) + await t.test( + "benchmarks a path draw and erase update in parallel", + async (/*t*/) => { + return runBidirectionalBenchmark( + "path draw and erase [parallel]" /* BENCHMARK */, + "plots/path-par-benchmark.tsv" /* FILENAME */, + ITERATIONSLIST /* ITERATIONSLIST */, + BLOCKSIZE /* BLOCKSIZE */, + pathDraw /* addData */, + pathErase /* eraseData */, + addOnInitFrontendParallel /* addOnInitFrontend */, + 5000 /* addBroadcastGroupTimeout */, + addOnBroadcastGroupParallel /* addOnBroadcastGroup */, + ".path-par-add-packets.json" /* addPacketsFilename */, + eraseOnInitFrontendParallel /* eraseOnInitFrontend */, + 5000 /* eraseBroadcastGroupTimeout */, + eraseOnBroadcastGroupParallel /* eraseOnBroadcastGroup */, + ".path-par-erase-packets.json" /* erasePacketsFilename */, + 5000 /* syncSendGroupTimeout */, + syncOnSendGroup /* syncOnSendGroup */, + ".path-par-sync-packets.json" /* syncPacketsFilename */, + addOnInitBackendParallel /* addOnInitBackend */, + 5000 /* addEventGroupTimeout */, + addOnEventGroupParallel /* addOnEventGroup */, + false /* addEventsCache */, + ".path-par-add-events.json" /* addEventsFilename */, + eraseOnInitBackendParallel /* eraseOnInitBackend */, + 5000 /* eraseEventGroupTimeout */, + eraseOnEventGroupParallel /* eraseOnEventGroupTimeout */, + false /* eraseEventsCache */, + ".path-par-erase-events.json" /* eraseEventsFilename */, + 5000 /* syncEventGroupTimeout */, + syncOnEventGroup /* syncOnEventGroup */, + false /* syncEventsCache */, + ".path-par-sync-events.json" /* syncEventsFilename */, + ) + }, + ) - it("communicates a single draw and erase update", () => { + await t.test("communicates a single draw and erase update", async (/*t*/) => { return runBidirectionalBenchmark( null /* BENCHMARK */, null /* FILENAME */, @@ -1379,7 +1452,7 @@ describe("drawing app mesh", () => { ) }) - it("communicates a path draw and erase update", () => { + await t.test("communicates a path draw and erase update", async (/*t*/) => { return runBidirectionalBenchmark( null /* BENCHMARK */, null /* FILENAME */, diff --git a/__tests__/benchmark.data.js b/__benchmarks__/data.js similarity index 94% rename from __tests__/benchmark.data.js rename to __benchmarks__/data.js index 86df444825ed4c4c413f9c8ac57aa43fd4d526c8..e91d0b99e85d387f8304c6c2fd92d0e877d74215 100644 --- a/__tests__/benchmark.data.js +++ b/__benchmarks__/data.js @@ -127,22 +127,22 @@ export const syncDone = { compressed: true, } -export const dotDraw = [[209, 88, 5.000000000000001, "#0000ff"]] +export const dotDraw = [[209, 88, 5.0, "#0000ff"]] export const dotErase = [[0, [[0, 0]]]] export const pathDraw = [ - [229, 147, 5.000000000000001, "#0000ff"], - [239, 149, 5.000000000000001, "#0000ff"], - [265, 154, 5.000000000000001, "#0000ff"], - [329, 158, 5.000000000000001, "#0000ff"], - [428, 168, 5.000000000000001, "#0000ff"], - [559, 172, 5.000000000000001, "#0000ff"], - [689, 176, 5.000000000000001, "#0000ff"], - [789, 176, 5.000000000000001, "#0000ff"], - [871, 178, 5.000000000000001, "#0000ff"], - [915, 179, 5.000000000000001, "#0000ff"], - [937, 179, 5.000000000000001, "#0000ff"], - [942, 179, 5.000000000000001, "#0000ff"], + [229, 147, 5.0, "#0000ff"], + [239, 149, 5.0, "#0000ff"], + [265, 154, 5.0, "#0000ff"], + [329, 158, 5.0, "#0000ff"], + [428, 168, 5.0, "#0000ff"], + [559, 172, 5.0, "#0000ff"], + [689, 176, 5.0, "#0000ff"], + [789, 176, 5.0, "#0000ff"], + [871, 178, 5.0, "#0000ff"], + [915, 179, 5.0, "#0000ff"], + [937, 179, 5.0, "#0000ff"], + [942, 179, 5.0, "#0000ff"], ] export const pathErase = [ [0, [[0, 0.030367582231477598]]], diff --git a/__benchmarks__/puppeteer.js b/__benchmarks__/puppeteer.js new file mode 100644 index 0000000000000000000000000000000000000000..275d7c8eac6961d06a8166e886e53556bb9c631f --- /dev/null +++ b/__benchmarks__/puppeteer.js @@ -0,0 +1,74 @@ +import puppeteer from "puppeteer" +import fs from "fs" +;(async () => { + const browser = await puppeteer.launch({ + headless: true, + args: ["--js-flags=--expose-gc"], + }) + const page = await browser.newPage() + + const done = new Promise((resolve) => { + page.on("console", (msg) => { + if (msg.type() == "debug") { + process.stderr.write(msg.text()) + } else if (msg.type() == "info") { + const { filename, title, iterations, results } = JSON.parse(msg.text()) + + if (title) { + const columns = ["iterations"] + + for (const title of results) { + columns.push(`${title}_timeLoc`) + columns.push(`${title}_encodeRAM`) + columns.push(`${title}_packets`) + columns.push(`${title}_size`) + columns.push(`${title}_timeRem`) + columns.push(`${title}_decodeRAM`) + columns.push(`${title}_events`) + } + + fs.writeFileSync( + filename, + `# Benchmark: ${title}\n# ${columns.join("\t")}\n`, + ) + } else { + const columns = [iterations] + + for (const title in results) { + const { + timeLoc, + encodeRAM, + packets, + size, + timeRem, + decodeRAM, + events, + } = results[title] + + columns.push(timeLoc) + columns.push(encodeRAM) + columns.push(packets) + columns.push(size) + columns.push(timeRem) + columns.push(decodeRAM) + columns.push(events) + } + + fs.appendFileSync(filename, `${columns.join("\t")}\n`) + } + } else if (msg.type() == "log") { + if (msg.text().startsWith("# failure")) { + resolve() + } + + process.stdout.write(msg.text() + "\n") + } + }) + }) + + await page.goto("http://localhost:3000/benchmarks.html").catch(console.error) + + await done + + await browser.close() +})() diff --git a/package-lock.json b/package-lock.json index 479b4d4f8adad9eacbcb9126a6fb55505decfa3f..24c0945598f4769fdbc1b536b177ac62e28577ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -961,6 +961,15 @@ "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" }, + "agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + }, "aggregate-error": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz", @@ -1009,6 +1018,12 @@ "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", "dev": true }, + "ansi-escape": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-escape/-/ansi-escape-1.1.0.tgz", + "integrity": "sha1-ithZ6Epp4P+Rd5aUeTqS5OjAXpk=", + "dev": true + }, "ansi-escapes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.0.tgz", @@ -2585,6 +2600,12 @@ "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", "dev": true }, + "buffer-shims": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", + "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=", + "dev": true + }, "buffer-xor": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", @@ -3886,6 +3907,21 @@ "is-symbol": "^1.0.2" } }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "dev": true + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "dev": true, + "requires": { + "es6-promise": "^4.0.3" + } + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -4361,6 +4397,18 @@ } } }, + "extract-zip": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz", + "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=", + "dev": true, + "requires": { + "concat-stream": "1.6.2", + "debug": "2.6.9", + "mkdirp": "0.5.1", + "yauzl": "2.4.1" + } + }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", @@ -4440,6 +4488,15 @@ "bser": "2.1.1" } }, + "fd-slicer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", + "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", + "dev": true, + "requires": { + "pend": "~1.2.0" + } + }, "figgy-pudding": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", @@ -5654,6 +5711,33 @@ "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", "dev": true }, + "https-proxy-agent": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-3.0.1.tgz", + "integrity": "sha512-+ML2Rbh6DAuee7d07tYGEKOEi2voWPUGan+ExdPbPW6Z3svq+JCqr0v8WmKPOkz1vOVykPCBSuobe7G8GJUtVg==", + "dev": true, + "requires": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, "human-signals": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", @@ -8095,6 +8179,12 @@ "json-parse-better-errors": "^1.0.1" } }, + "parse-ms": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-1.0.1.tgz", + "integrity": "sha1-VjRtR0nXjyNDDKDHE4UK75GqNh0=", + "dev": true + }, "parse-passwd": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", @@ -8209,6 +8299,12 @@ "sha.js": "^2.4.8" } }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -8321,6 +8417,15 @@ } } }, + "pretty-ms": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-3.2.0.tgz", + "integrity": "sha512-ZypexbfVUGTFxb0v+m1bUyy92DHe5SyYlnyY0msyms5zd3RwyvNgyxZZsXXgoyzlxjx5MiqtXUdhUfvQbe0A2Q==", + "dev": true, + "requires": { + "parse-ms": "^1.0.0" + } + }, "private": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", @@ -8379,6 +8484,12 @@ "ipaddr.js": "1.9.0" } }, + "proxy-from-env": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", + "integrity": "sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4=", + "dev": true + }, "prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -8386,9 +8497,9 @@ "dev": true }, "psl": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.6.0.tgz", - "integrity": "sha512-SYKKmVel98NCOYXpkwUqZqh0ahZeeKfmisiLIcEZdsb+WbLv02g/dI5BUmZnIyOe7RzZtLax81nnb2HbvC2tzA==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz", + "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==", "dev": true }, "public-encrypt": { @@ -8444,6 +8555,45 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, + "puppeteer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-2.0.0.tgz", + "integrity": "sha512-t3MmTWzQxPRP71teU6l0jX47PHXlc4Z52sQv4LJQSZLq1ttkKS2yGM3gaI57uQwZkNaoGd0+HPPMELZkcyhlqA==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "extract-zip": "^1.6.6", + "https-proxy-agent": "^3.0.0", + "mime": "^2.0.3", + "progress": "^2.0.1", + "proxy-from-env": "^1.0.0", + "rimraf": "^2.6.1", + "ws": "^6.1.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "mime": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", + "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, "qrcode-terminal": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.10.0.tgz", @@ -8501,6 +8651,12 @@ "unpipe": "1.0.0" } }, + "re-emitter": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/re-emitter/-/re-emitter-1.1.3.tgz", + "integrity": "sha1-+p4xn/3u6zWycpbvDz03TawvUqc=", + "dev": true + }, "react-is": { "version": "16.12.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz", @@ -9396,12 +9552,12 @@ "dev": true }, "source-map-resolve": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", - "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", "dev": true, "requires": { - "atob": "^2.1.1", + "atob": "^2.1.2", "decode-uri-component": "^0.2.0", "resolve-url": "^0.2.1", "source-map-url": "^0.4.0", @@ -9464,6 +9620,15 @@ "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", "dev": true }, + "split": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.0.tgz", + "integrity": "sha1-xDlc5oOrzSVLwo/h2rtuXCfc/64=", + "dev": true, + "requires": { + "through": "2" + } + }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -9769,6 +9934,80 @@ } } }, + "tap-out": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tap-out/-/tap-out-2.1.0.tgz", + "integrity": "sha512-LJE+TBoVbOWhwdz4+FQk40nmbIuxJLqaGvj3WauQw3NYYU5TdjoV3C0x/yq37YAvVyi+oeBXmWnxWSjJ7IEyUw==", + "dev": true, + "requires": { + "re-emitter": "1.1.3", + "readable-stream": "2.2.9", + "split": "1.0.0", + "trim": "0.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, + "readable-stream": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.9.tgz", + "integrity": "sha1-z3jsb0ptHrQ9JkiMrJfwQudLf8g=", + "dev": true, + "requires": { + "buffer-shims": "~1.0.0", + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "string_decoder": "~1.0.0", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "tap-summary": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tap-summary/-/tap-summary-4.0.0.tgz", + "integrity": "sha1-kyFIYrpGfFCgnzeOoOXtxd0EQz0=", + "dev": true, + "requires": { + "ansi-escape": "^1.0.1", + "commander": "^2.9.0", + "figures": "^2.0.0", + "pretty-ms": "^3.0.0", + "tap-out": "^2.0.0" + }, + "dependencies": { + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + } + } + }, "tapable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", @@ -10697,6 +10936,12 @@ "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "dev": true }, + "trim": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", + "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=", + "dev": true + }, "trim-right": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", @@ -11584,6 +11829,15 @@ "decamelize": "^1.2.0" } }, + "yauzl": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", + "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", + "dev": true, + "requires": { + "fd-slicer": "~1.0.1" + } + }, "yeast": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", @@ -11593,7 +11847,28 @@ "version": "file:src/yjs", "requires": { "debug": "^2.6.3" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } } + }, + "zora": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/zora/-/zora-3.1.8.tgz", + "integrity": "sha512-AArEyKiLWi3eLXW2uRbfPvANfSQgV8VHoCuXCihCTQyUv7brFrghGbsUqKxqucc+QodQ1G2+O8Gpsz8RVpeiRQ==", + "dev": true } } } diff --git a/package.json b/package.json index ab69222048ae995b8b9005bc228da2b79926fead..476d27c897498ab9898bb378de66ddc44ed29786 100644 --- a/package.json +++ b/package.json @@ -13,12 +13,13 @@ "build": "webpack --config webpack.prod.js", "build:analyze": "webpack --env.analyze --config webpack.prod.js", "build:dev": "webpack --config webpack.dev.js", + "build:bench": "webpack --config webpack.bench.js", "watch": "webpack --watch --config webpack.dev.js", "start": "node --experimental-modules src/server.js", - "test": "jest --testPathIgnorePatterns .*.data.js .*benchmark.test.js src/liowebrtc src/rtcpeerconnection src/signalbuddy src/yjs src/tiny-worker", - "test-changed": "jest --only-changed --testPathIgnorePatterns __tests__/*.data.js src/liowebrtc src/rtcpeerconnection src/signalbuddy src/yjs src/tiny-worker src/drawing-crdt", - "test-coverage": "jest --coverage --testPathIgnorePatterns __tests__/*.data.js src/liowebrtc src/rtcpeerconnection src/signalbuddy src/yjs src/tiny-worker src/drawing-crdt", - "test-benchmark": "jest --testPathPattern .*benchmark.test.js --testPathIgnorePatterns .*.data.js src/liowebrtc src/rtcpeerconnection src/signalbuddy src/yjs src/tiny-worker src/drawing-crdt --runInBand", + "test": "jest --testPathIgnorePatterns src/liowebrtc src/rtcpeerconnection src/signalbuddy src/yjs src/tiny-worker", + "test-changed": "jest --only-changed --testPathIgnorePatterns src/liowebrtc src/rtcpeerconnection src/signalbuddy src/yjs src/tiny-worker src/drawing-crdt", + "test-coverage": "jest --coverage --testPathIgnorePatterns src/liowebrtc src/rtcpeerconnection src/signalbuddy src/yjs src/tiny-worker src/drawing-crdt", + "benchmarks": "node --experimental-modules __benchmarks__/puppeteer.js | npx tap-summary", "test-e2e:peer1": "testcafe chrome:headless __e2e_tests__/peer1.e2e.js", "test-e2e:peer2": "testcafe chrome:headless __e2e_tests__/peer2.e2e.js", "test-e2e": "run-p test-e2e:*", @@ -62,6 +63,8 @@ "jest": "^24.9.0", "npm-run-all": "^4.1.5", "prettier": "^1.18.2", + "puppeteer": "^2.0.0", + "tap-summary": "^4.0.0", "testcafe": "^1.5.0", "tiny-worker": "file:src/tiny-worker", "webpack": "^4.41.0", @@ -69,7 +72,8 @@ "webpack-cli": "^3.3.9", "webpack-merge": "^4.2.2", "webpack-preprocessor-loader": "^1.1.2", - "yaeti": "^1.0.2" + "yaeti": "^1.0.2", + "zora": "^3.1.8" }, "pre-commit": [ "lint", diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..4ff127ef17c8b3eb9e4b5a78ba4ddc76d42f98fd Binary files /dev/null and b/public/favicon.ico differ diff --git a/src/connection/MockConnection.js b/src/connection/MockConnection.js index 2fa9825675d0f5e13fe8d0be23d871fed665fb25..f01abd3fcdbaade836da741762095d875c30942c 100644 --- a/src/connection/MockConnection.js +++ b/src/connection/MockConnection.js @@ -1,66 +1,62 @@ -export const getUserID = jest.fn((uid) => uid) -export const getPeerHandle = jest.fn((/*uid*/) => undefined) -export const getPeerFootprint = jest.fn((/*uid*/) => - Promise.resolve(Date.now())) +export const userID = { uuid: null } export const sendListener = { callback: null } -export const send = jest.fn((uid, channel, message) => { - if (sendListener.callback) { - sendListener.callback(uid, channel, message) - } -}) -const sendMockClear = send.mockClear -send.mockClear = () => { - sendListener.callback = null - sendMockClear() -} - export const broadcastListener = { callback: null } -export const broadcast = jest.fn((channel, message) => { - if (broadcastListener.callback) { - broadcastListener.callback(channel, message) - } -}) -const broadcastMockClear = broadcast.mockClear -broadcast.mockClear = () => { - broadcastListener.callback = null - broadcastMockClear() -} - -export const terminatePeer = jest.fn() -export const destructor = jest.fn(() => eventListeners.clear()) const eventListeners = new Map() export const getEventListener = (room, event) => eventListeners.get(`${room}:${event}`) -export const addEventListener = jest.fn((room, event, callback) => - eventListeners.set(`${room}:${event}`, callback), -) -const addEventListenerMockClear = addEventListener.mockClear -addEventListener.mockClear = () => { - eventListeners.clear() - addEventListenerMockClear() -} -const MockConnection = jest.fn().mockImplementation(({ room }) => { - setTimeout( - () => - getEventListener(room, "roomJoined") && - getEventListener(room, "roomJoined")(), - 0, - ) +class MockConnection { + constructor({ room }) { + this.room = room + + setTimeout( + () => + getEventListener(room, "roomJoined") && + getEventListener(room, "roomJoined")(), + 0, + ) + } + + getUserID() { + return userID.uuid + } + + getPeerHandle(/*uid*/) { + return undefined + } - return { - getUserID: () => getUserID(room), - getPeerHandle, - getPeerFootprint, - send, - broadcast, - terminatePeer, - destructor, - addEventListener: (event, callback) => - addEventListener(room, event, callback), + getPeerFootprint(/*uid*/) { + return Promise.resolve(Date.now()) } -}) + + send(uid, channel, message) { + if (sendListener.callback) { + sendListener.callback(uid, channel, message) + } + } + + broadcast(channel, message) { + if (broadcastListener.callback) { + broadcastListener.callback(channel, message) + } + } + + terminatePeer() { + // Twiddle thumbs + } + + destructor() { + sendListener.callback = null + broadcastListener.callback = null + + eventListeners.clear() + } + + addEventListener(event, callback) { + eventListeners.set(`${this.room}:${event}`, callback) + } +} export default MockConnection diff --git a/src/drawing-crdt b/src/drawing-crdt index 5a239921c305a8b3251b3ef393fd79930df4bf8e..c8d9e04fc2b710fc2152407013f24b044c5c91c7 160000 --- a/src/drawing-crdt +++ b/src/drawing-crdt @@ -1 +1 @@ -Subproject commit 5a239921c305a8b3251b3ef393fd79930df4bf8e +Subproject commit c8d9e04fc2b710fc2152407013f24b044c5c91c7 diff --git a/src/queue.js b/src/queue.js index 8ba36ca246b06915ec07614328053c9287581561..b728c51dace01cd362934861af9efa34730f8afd 100644 --- a/src/queue.js +++ b/src/queue.js @@ -20,7 +20,14 @@ onmessage = (event) => { if (event.data.method == "send" || event.data.method == "broadcast") { let message = event.data.message - const compressed = typeof message == "object" + const compressed = !( + message == undefined || + message instanceof String || + typeof message == "string" || + message instanceof Uint8Array || + message.message instanceof Uint8Array || + message.message == undefined + ) const uuid = uuidv4() //console.log("send in", message) diff --git a/src/wasm-crdt.js b/src/wasm-crdt.js index 0adcf0eaabc3703d870038a6dadf8aee9da41178..0b5bf4d719a02aa761bd0c47dd63976d70df4ac2 100644 --- a/src/wasm-crdt.js +++ b/src/wasm-crdt.js @@ -63,7 +63,8 @@ export default class WasmCRDTWrapper { this.interval = setInterval(() => { this.crdt.fetch_events() this.crdt.fetch_deltas() - }, 16) + // TODO: pass an option here + }, 0) } destroy() { diff --git a/webpack.bench.js b/webpack.bench.js new file mode 100644 index 0000000000000000000000000000000000000000..60a8fc0e8ac3fdb506f951db198956ecb73af2f8 --- /dev/null +++ b/webpack.bench.js @@ -0,0 +1,18 @@ +const merge = require("webpack-merge") +const common = require("./webpack.common.js") +const BundleAnalyzerPlugin = require("webpack-bundle-analyzer") + .BundleAnalyzerPlugin + +module.exports = (env) => { + const config = merge(common, { + mode: "production", + entry: { + benchmarks: "./__benchmarks__/benchmarks.js", + }, + plugins: env && env.analyze ? [new BundleAnalyzerPlugin()] : [], + }) + + delete config.entry.app + + return config +}