From fe3f746d54c279780ef19244746a71fc380c10ef Mon Sep 17 00:00:00 2001 From: Brian Botha Date: Fri, 10 May 2024 17:11:51 +1000 Subject: [PATCH 1/3] wip: added benchmark for `listReferencesGenerator` [ci skip] --- benches/index.ts | 5 +- benches/suites/git/git_utils_fs.ts | 115 +++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 benches/suites/git/git_utils_fs.ts diff --git a/benches/index.ts b/benches/index.ts index 553f154f8..cf66dcb3f 100644 --- a/benches/index.ts +++ b/benches/index.ts @@ -12,7 +12,10 @@ async function main(): Promise { for await (const suitePath of fsWalk(suitesPath)) { // Skip over non-ts and non-js files const ext = path.extname(suitePath); - if (ext !== '.ts' && ext !== '.js') { + if ( + (ext !== '.ts' && ext !== '.js') || + path.basename(suitePath) !== 'git_utils_fs.ts' + ) { continue; } const suite: () => Promise = (await import(suitePath)).default; diff --git a/benches/suites/git/git_utils_fs.ts b/benches/suites/git/git_utils_fs.ts new file mode 100644 index 000000000..cd3e0809b --- /dev/null +++ b/benches/suites/git/git_utils_fs.ts @@ -0,0 +1,115 @@ +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import b from 'benny'; +import { EncryptedFS } from 'encryptedfs'; +import { summaryName, suiteCommon } from '../../utils'; +import * as gitTestUtils from '../../../tests/git/utils'; +import * as gitUtils from '../../../src/git/utils'; +import * as keysUtils from '../../../src/keys/utils'; + +async function main() { + // Setting up repo + const dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const testGitState = { + author: 'tester', + commits: [ + { + message: 'commit1', + files: [ + { + name: 'file1', + contents: 'this is a file', + }, + ], + }, + { + message: 'commit2', + files: [ + { + name: 'file2', + contents: 'this is another file', + }, + ], + }, + { + message: 'commit3', + files: [ + { + name: 'file1', + contents: 'this is a changed file', + }, + ], + }, + ], + }; + + // Creating state for fs + const dirFs = path.join(dataDir, 'repository'); + const gitdirFs = path.join(dirFs, '.git'); + const gitDirsFs = { + fs: fs as any, + dir: dirFs, + gitDir: gitdirFs, + gitdir: gitdirFs, + }; + // Creating simple state + await gitTestUtils.createGitRepo({ + ...gitDirsFs, + ...testGitState, + }); + + // Creating state for efs + const logger = new Logger('listReferencesGenerator Test', LogLevel.WARN, [ + new StreamHandler(), + ]); + const dbKey = keysUtils.generateKey(); + const efs = await EncryptedFS.createEncryptedFS({ + dbKey, + dbPath: dataDir, + logger, + }); + await efs.start(); + + const dirEfs = path.join(efs.cwd, 'repository'); + const gitdirEfs = path.join(dirEfs, '.git'); + const gitDirsEfs = { + fs: efs as any, + dir: dirEfs, + gitDir: gitdirEfs, + gitdir: gitdirEfs, + }; + await gitTestUtils.createGitRepo({ + ...gitDirsEfs, + ...testGitState, + }); + + const summary = await b.suite( + summaryName(__filename), + b.add('listReferencesGenerator with fs', async () => { + for await (const _ of gitUtils.listReferencesGenerator({ + ...gitDirsFs, + })) { + // We just want to iterate to test the speed of listReferencesGenerator + } + }), + b.add('listReferencesGenerator with efs', async () => { + for await (const _ of gitUtils.listReferencesGenerator({ + ...gitDirsEfs, + })) { + // We just want to iterate to test the speed of listReferencesGenerator + } + }), + ...suiteCommon, + ); + return summary; +} + +if (require.main === module) { + void main(); +} + +export default main; From 2c64a81587cd3c84ce88bcc1b2bb94a973a62c7f Mon Sep 17 00:00:00 2001 From: Brian Botha Date: Mon, 13 May 2024 16:51:32 +1000 Subject: [PATCH 2/3] wip: expanding benchmarks [ci skip] --- benches/index.ts | 2 +- benches/suites/git/gitClone.ts | 122 ++++++++++++++++ .../suites/git/http_advertiseRefGenerator.ts | 115 +++++++++++++++ .../suites/git/http_generatePackRequest.ts | 138 ++++++++++++++++++ 4 files changed, 376 insertions(+), 1 deletion(-) create mode 100644 benches/suites/git/gitClone.ts create mode 100644 benches/suites/git/http_advertiseRefGenerator.ts create mode 100644 benches/suites/git/http_generatePackRequest.ts diff --git a/benches/index.ts b/benches/index.ts index cf66dcb3f..765200c00 100644 --- a/benches/index.ts +++ b/benches/index.ts @@ -14,7 +14,7 @@ async function main(): Promise { const ext = path.extname(suitePath); if ( (ext !== '.ts' && ext !== '.js') || - path.basename(suitePath) !== 'git_utils_fs.ts' + path.basename(suitePath) !== 'gitClone.ts' ) { continue; } diff --git a/benches/suites/git/gitClone.ts b/benches/suites/git/gitClone.ts new file mode 100644 index 000000000..93ff2ce6e --- /dev/null +++ b/benches/suites/git/gitClone.ts @@ -0,0 +1,122 @@ +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import b from 'benny'; +import { EncryptedFS } from 'encryptedfs'; +import git from 'isomorphic-git'; +import { summaryName, suiteCommon } from '../../utils'; +import * as gitTestUtils from '../../../tests/git/utils'; +import * as keysUtils from '../../../src/keys/utils'; + +async function main() { + // Setting up repo + const dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const testGitState = { + author: 'tester', + commits: [ + { + message: 'commit1', + files: [ + { + name: 'file1', + contents: 'this is a file', + }, + ], + }, + { + message: 'commit2', + files: [ + { + name: 'file2', + contents: 'this is another file', + }, + ], + }, + { + message: 'commit3', + files: [ + { + name: 'file1', + contents: 'this is a changed file', + }, + ], + }, + ], + }; + + // Creating state for fs + const dirFs = path.join(dataDir, 'repository'); + const gitdirFs = path.join(dirFs, '.git'); + const gitDirsFs = { + fs: fs as any, + dir: dirFs, + gitDir: gitdirFs, + gitdir: gitdirFs, + }; + // Creating simple state + await gitTestUtils.createGitRepo({ + ...gitDirsFs, + ...testGitState, + }); + + // Creating state for efs + const logger = new Logger('generatePackRequest Test', LogLevel.WARN, [ + new StreamHandler(), + ]); + const dbKey = keysUtils.generateKey(); + const efs = await EncryptedFS.createEncryptedFS({ + dbKey, + dbPath: dataDir, + logger, + }); + await efs.start(); + + const dirEfs = path.join(efs.cwd, 'repository'); + const gitdirEfs = path.join(dirEfs, '.git'); + const gitDirsEfs = { + fs: efs as any, + dir: dirEfs, + gitDir: gitdirEfs, + gitdir: gitdirEfs, + }; + await gitTestUtils.createGitRepo({ + ...gitDirsEfs, + ...testGitState, + }); + + // Creating RPC + + const summary = await b.suite( + summaryName(__filename), + b.add('git clone with fs', async () => { + await git.clone({ + fs, + dir: gitDirsFs.dir, + http: { request: gitTestUtils.request(gitDirsFs) }, + url: 'http://', + }); + }), + b.add('git clone with efs', async () => { + await git.clone({ + fs: efs, + dir: gitDirsEfs.dir, + http: { request: gitTestUtils.request(gitDirsEfs) }, + url: 'http://', + }); + }), + b.add('git clone with rpc', async () => { + // TODO: run test with request over RPC. + }), + ...suiteCommon, + ); + return summary; +} + +if (require.main === module) { + void main(); +} + +export default main; diff --git a/benches/suites/git/http_advertiseRefGenerator.ts b/benches/suites/git/http_advertiseRefGenerator.ts new file mode 100644 index 000000000..e8b017bff --- /dev/null +++ b/benches/suites/git/http_advertiseRefGenerator.ts @@ -0,0 +1,115 @@ +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import b from 'benny'; +import { EncryptedFS } from 'encryptedfs'; +import { summaryName, suiteCommon } from '../../utils'; +import * as gitTestUtils from '../../../tests/git/utils'; +import * as gitHttp from '../../../src/git/http'; +import * as keysUtils from '../../../src/keys/utils'; + +async function main() { + // Setting up repo + const dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const testGitState = { + author: 'tester', + commits: [ + { + message: 'commit1', + files: [ + { + name: 'file1', + contents: 'this is a file', + }, + ], + }, + { + message: 'commit2', + files: [ + { + name: 'file2', + contents: 'this is another file', + }, + ], + }, + { + message: 'commit3', + files: [ + { + name: 'file1', + contents: 'this is a changed file', + }, + ], + }, + ], + }; + + // Creating state for fs + const dirFs = path.join(dataDir, 'repository'); + const gitdirFs = path.join(dirFs, '.git'); + const gitDirsFs = { + fs: fs as any, + dir: dirFs, + gitDir: gitdirFs, + gitdir: gitdirFs, + }; + // Creating simple state + await gitTestUtils.createGitRepo({ + ...gitDirsFs, + ...testGitState, + }); + + // Creating state for efs + const logger = new Logger('advertiseRefGenerator Test', LogLevel.WARN, [ + new StreamHandler(), + ]); + const dbKey = keysUtils.generateKey(); + const efs = await EncryptedFS.createEncryptedFS({ + dbKey, + dbPath: dataDir, + logger, + }); + await efs.start(); + + const dirEfs = path.join(efs.cwd, 'repository'); + const gitdirEfs = path.join(dirEfs, '.git'); + const gitDirsEfs = { + fs: efs as any, + dir: dirEfs, + gitDir: gitdirEfs, + gitdir: gitdirEfs, + }; + await gitTestUtils.createGitRepo({ + ...gitDirsEfs, + ...testGitState, + }); + + const summary = await b.suite( + summaryName(__filename), + b.add('advertiseRefGenerator with fs', async () => { + for await (const _ of gitHttp.advertiseRefGenerator({ + ...gitDirsFs, + })) { + // We just want to iterate to test the speed of listReferencesGenerator + } + }), + b.add('advertiseRefGenerator with efs', async () => { + for await (const _ of gitHttp.advertiseRefGenerator({ + ...gitDirsEfs, + })) { + // We just want to iterate to test the speed of listReferencesGenerator + } + }), + ...suiteCommon, + ); + return summary; +} + +if (require.main === module) { + void main(); +} + +export default main; diff --git a/benches/suites/git/http_generatePackRequest.ts b/benches/suites/git/http_generatePackRequest.ts new file mode 100644 index 000000000..a0dd0d1a5 --- /dev/null +++ b/benches/suites/git/http_generatePackRequest.ts @@ -0,0 +1,138 @@ +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import b from 'benny'; +import { EncryptedFS } from 'encryptedfs'; +import git from 'isomorphic-git'; +import { summaryName, suiteCommon } from '../../utils'; +import * as gitTestUtils from '../../../tests/git/utils'; +import * as gitHttp from '../../../src/git/http'; +import * as keysUtils from '../../../src/keys/utils'; + +async function main() { + // Setting up repo + const dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const testGitState = { + author: 'tester', + commits: [ + { + message: 'commit1', + files: [ + { + name: 'file1', + contents: 'this is a file', + }, + ], + }, + { + message: 'commit2', + files: [ + { + name: 'file2', + contents: 'this is another file', + }, + ], + }, + { + message: 'commit3', + files: [ + { + name: 'file1', + contents: 'this is a changed file', + }, + ], + }, + ], + }; + + // Creating state for fs + const dirFs = path.join(dataDir, 'repository'); + const gitdirFs = path.join(dirFs, '.git'); + const gitDirsFs = { + fs: fs as any, + dir: dirFs, + gitDir: gitdirFs, + gitdir: gitdirFs, + }; + // Creating simple state + await gitTestUtils.createGitRepo({ + ...gitDirsFs, + ...testGitState, + }); + + // Creating state for efs + const logger = new Logger('generatePackRequest Test', LogLevel.WARN, [ + new StreamHandler(), + ]); + const dbKey = keysUtils.generateKey(); + const efs = await EncryptedFS.createEncryptedFS({ + dbKey, + dbPath: dataDir, + logger, + }); + await efs.start(); + + const dirEfs = path.join(efs.cwd, 'repository'); + const gitdirEfs = path.join(dirEfs, '.git'); + const gitDirsEfs = { + fs: efs as any, + dir: dirEfs, + gitDir: gitdirEfs, + gitdir: gitdirEfs, + }; + await gitTestUtils.createGitRepo({ + ...gitDirsEfs, + ...testGitState, + }); + + const headFs = ( + await git.log({ + ...gitDirsFs, + depth: 1, + }) + )[0].oid; + const headEfs = ( + await git.log({ + ...gitDirsEfs, + depth: 1, + }) + )[0].oid; + function generateBody(id: string) { + return Buffer.from( + `0060want ${id} side-band-64k agent=git/isomorphic-git@1.24.5\n00000009done\n`, + ); + } + const bodyFs: Array = [generateBody(headFs)]; + const bodyEfs: Array = [generateBody(headEfs)]; + + const summary = await b.suite( + summaryName(__filename), + b.add('generatePackRequest with fs', async () => { + for await (const _ of gitHttp.generatePackRequest({ + ...gitDirsFs, + body: bodyFs, + })) { + // We just want to iterate to test the speed of listReferencesGenerator + } + }), + b.add('generatePackRequest with efs', async () => { + for await (const _ of gitHttp.generatePackRequest({ + ...gitDirsEfs, + body: bodyEfs, + })) { + // We just want to iterate to test the speed of listReferencesGenerator + } + }), + ...suiteCommon, + ); + return summary; +} + +if (require.main === module) { + void main(); +} + +export default main; From 2a2013e4510f97bafaf1324e4cd246c996fb24b9 Mon Sep 17 00:00:00 2001 From: Brian Botha Date: Tue, 14 May 2024 15:29:27 +1000 Subject: [PATCH 3/3] wip: expanding benchmarks [ci skip] --- benches/suites/git/gitClone.ts | 220 ++++++++++++++++++++++++++++++++- 1 file changed, 218 insertions(+), 2 deletions(-) diff --git a/benches/suites/git/gitClone.ts b/benches/suites/git/gitClone.ts index 93ff2ce6e..70519da07 100644 --- a/benches/suites/git/gitClone.ts +++ b/benches/suites/git/gitClone.ts @@ -1,12 +1,20 @@ +import type { ContextTimed } from '@matrixai/contexts'; +import type { ReadableWritablePair } from 'stream/web'; +import type { JSONObject, JSONRPCRequest, RPCStream } from '@matrixai/rpc'; +import type { POJO } from '@'; import fs from 'fs'; import path from 'path'; import os from 'os'; +import { ReadableStream, TransformStream } from 'stream/web'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import b from 'benny'; import { EncryptedFS } from 'encryptedfs'; import git from 'isomorphic-git'; +import { RawCaller, RawHandler, RPCClient, RPCServer } from '@matrixai/rpc'; +import * as utils from '@/utils'; import { summaryName, suiteCommon } from '../../utils'; import * as gitTestUtils from '../../../tests/git/utils'; +import * as gitHttp from '../../../src/git/http'; import * as keysUtils from '../../../src/keys/utils'; async function main() { @@ -87,7 +95,202 @@ async function main() { ...testGitState, }); + class GitAdvertiseHandler extends RawHandler<{ + fs: EncryptedFS; + dir: string; + gitDir: string; + }> { + public handle = async ( + input: [JSONRPCRequest, ReadableStream], + ): Promise<[JSONObject, ReadableStream]> => { + const { fs, dir, gitDir } = this.container; + const [, inputStream] = input; + await inputStream.cancel(); + + let advertiseRefGenerator: AsyncGenerator; + const stream = new ReadableStream({ + start: async () => { + advertiseRefGenerator = gitHttp.advertiseRefGenerator({ + fs: fs as any, + dir, + gitDir, + }); + }, + pull: async (controller) => { + const result = await advertiseRefGenerator.next(); + if (result.done) { + controller.close(); + return; + } else { + controller.enqueue(result.value); + } + }, + cancel: async (reason) => { + await advertiseRefGenerator.throw(reason).catch(() => {}); + }, + }); + return [{}, stream]; + }; + } + + class GitPackHandler extends RawHandler<{ + fs: EncryptedFS; + dir: string; + gitDir: string; + }> { + public handle = async ( + input: [JSONRPCRequest, ReadableStream], + ): Promise<[JSONObject, ReadableStream]> => { + const { fs, dir, gitDir } = this.container; + const [, inputStream] = input; + + let gitPackgenerator: AsyncGenerator; + const stream = new ReadableStream({ + start: async () => { + const body: Array = []; + for await (const message of inputStream) { + body.push(Buffer.from(message)); + } + gitPackgenerator = gitHttp.generatePackRequest({ + fs: fs as any, + dir, + gitDir, + body, + }); + }, + pull: async (controller) => { + const result = await gitPackgenerator.next(); + if (result.done) { + controller.close(); + return; + } else { + controller.enqueue(result.value); + } + }, + cancel: async (reason) => { + await gitPackgenerator.throw(reason).catch(() => {}); + }, + }); + return [{}, stream]; + }; + } + // Creating RPC + const rpcServer = new RPCServer({ + logger: logger.getChild('RPCServer'), + }); + await rpcServer.start({ + manifest: { + gitAdvertiseFs: new GitAdvertiseHandler(gitDirsFs), + gitAdvertiseEfs: new GitAdvertiseHandler(gitDirsEfs), + gitPackFs: new GitPackHandler(gitDirsFs), + gitPackEfs: new GitPackHandler(gitDirsEfs), + }, + }); + + function createPassthroughStream() { + const forwardPass = new TransformStream({ + transform: (chunk, controller) => { + // Console.log('forward -- ', chunk.toString()); + controller.enqueue(chunk); + }, + }); + const reversePass = new TransformStream({ + transform: (chunk, controller) => { + // Console.log('reverse -- ', chunk.toString()); + controller.enqueue(chunk); + }, + }); + const clientPair: ReadableWritablePair = { + readable: reversePass.readable, + writable: forwardPass.writable, + }; + const serverPair: ReadableWritablePair = { + readable: forwardPass.readable, + writable: reversePass.writable, + }; + return { + clientPair, + serverPair, + }; + } + + const rpcClient = new RPCClient({ + manifest: { + gitAdvertiseFs: new RawCaller(), + gitAdvertiseEfs: new RawCaller(), + gitPackFs: new RawCaller(), + gitPackEfs: new RawCaller(), + }, + async streamFactory( + ctx: ContextTimed, + ): Promise> { + const { clientPair, serverPair } = createPassthroughStream< + Uint8Array, + Uint8Array + >(); + rpcServer.handleStream({ + ...serverPair, + cancel: () => {}, + }); + return { + ...clientPair, + cancel: () => {}, + }; + }, + }); + + function request({ type }: { type: 'fs' | 'efs' }) { + return async ({ + url, + method = 'GET', + headers = {}, + body = [Buffer.from('')], + }: { + url: string; + method: string; + headers: POJO; + body: Array; + }) => { + // Console.log('body', body.map(v => v.toString())) + if (method === 'GET') { + // Send back the GET request info response + const advertiseRefResponse = + type === 'fs' + ? await rpcClient.methods.gitAdvertiseFs({}) + : await rpcClient.methods.gitAdvertiseEfs({}); + // Await advertiseRefResponse.writable.close(); + + return { + url: url, + method: method, + body: advertiseRefResponse.readable, + headers: headers, + statusCode: 200, + statusMessage: 'OK', + }; + } else if (method === 'POST') { + const packResponse = + type === 'fs' + ? await rpcClient.methods.gitPackFs({}) + : await rpcClient.methods.gitPackEfs({}); + const writer = packResponse.writable.getWriter(); + for (const buffer of body) await writer.write(buffer); + await writer.close(); + + return { + url: url, + method: method, + body: packResponse.readable, + headers: headers, + statusCode: 200, + statusMessage: 'OK', + }; + } else { + utils.never(); + } + }; + } const summary = await b.suite( summaryName(__filename), @@ -107,8 +310,21 @@ async function main() { url: 'http://', }); }), - b.add('git clone with rpc', async () => { - // TODO: run test with request over RPC. + b.add('git clone with fs + rpc', async () => { + await git.clone({ + fs: efs, + dir: gitDirsEfs.dir, + http: { request: request({ type: 'fs' }) }, + url: 'http://', + }); + }), + b.add('git clone with efs + rpc', async () => { + await git.clone({ + fs: efs, + dir: gitDirsEfs.dir, + http: { request: request({ type: 'efs' }) }, + url: 'http://', + }); }), ...suiteCommon, );