From 2d6eaac96a1205c6762ee927054ca66969421d14 Mon Sep 17 00:00:00 2001 From: moonrailgun Date: Fri, 16 Jun 2023 22:28:07 +0800 Subject: [PATCH] perf(cli): optimize the experience of the benchmark command in the case of large data volume pressure test --- apps/cli/package.json | 3 +- .../cli/src/commands/benchmark/connections.ts | 119 +++++++++++------- apps/cli/src/commands/benchmark/register.ts | 48 +++++-- pnpm-lock.yaml | 13 +- 4 files changed, 125 insertions(+), 58 deletions(-) diff --git a/apps/cli/package.json b/apps/cli/package.json index 8f74f087..48181702 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -1,6 +1,6 @@ { "name": "tailchat-cli", - "version": "1.5.9", + "version": "1.5.10", "description": "A Command line interface of tailchat", "bin": { "tailchat": "./bin/cli" @@ -54,6 +54,7 @@ "nodemailer": "^6.7.2", "ora": "5.4.1", "p-all": "2.1.0", + "p-map": "^4.0.0", "p-series": "2.1.0", "pidusage": "^3.0.2", "plop": "^3.0.5", diff --git a/apps/cli/src/commands/benchmark/connections.ts b/apps/cli/src/commands/benchmark/connections.ts index 592bc260..785bfc3c 100644 --- a/apps/cli/src/commands/benchmark/connections.ts +++ b/apps/cli/src/commands/benchmark/connections.ts @@ -4,6 +4,7 @@ import msgpackParser from 'socket.io-msgpack-parser'; import fs from 'fs-extra'; import ora from 'ora'; import randomString from 'crypto-random-string'; +import pMap from 'p-map'; const CLIENT_CREATION_INTERVAL_IN_MS = 5; @@ -19,6 +20,11 @@ export const benchmarkConnectionsCommand: CommandModule = { type: 'string', default: './accounts', }) + .option('concurrency', { + describe: 'Concurrency when create connection', + type: 'number', + default: 1, + }) .option('groupId', { describe: 'Group Id which send Message', type: 'string', @@ -26,12 +32,19 @@ export const benchmarkConnectionsCommand: CommandModule = { .option('converseId', { describe: 'Converse Id which send Message', type: 'string', + }) + .option('messageNum', { + describe: 'Times which send Message', + type: 'number', + default: 1, }), async handler(args) { const url = args.url as string; const file = args.file as string; const groupId = args.groupId as string; const converseId = args.converseId as string; + const messageNum = args.messageNum as number; + const concurrency = args.concurrency as number; console.log('Reading account tokens from', file); const account = await fs.readFile(file as string, { @@ -39,64 +52,42 @@ export const benchmarkConnectionsCommand: CommandModule = { }); const sockets = await createClients( url as string, - account.split('\n').map((s) => s.trim()) + account.split('\n').map((s) => s.trim()), + concurrency ); if (groupId && converseId) { // send message test - const randomMessage = randomString({ length: 16 }); - const spinner = ora() - .info(`Start message receive test, message: ${randomMessage}`) - .start(); - const start = Date.now(); - let receiveCount = 0; - const len = sockets.length; - - function receivedCallback() { - receiveCount += 1; - spinner.text = `Receive: ${receiveCount}/${len}`; - - if (receiveCount === len) { - spinner.succeed( - `All client received, usage: ${Date.now() - start}ms` - ); - } + for (let i = 0; i < messageNum; i++) { + console.log('Start send message test:', i + 1); + await sendMessage(sockets, groupId, converseId); } - - sockets.forEach((socket) => { - socket.on('notify:chat.message.add', (message) => { - const content = message.content; - - if (message.converseId === converseId && randomMessage === content) { - receivedCallback(); - } - }); - }); - - sockets[0].emit('chat.message.sendMessage', { - groupId, - converseId, - content: randomMessage, - }); } }, }; async function createClients( url: string, - accountTokens: string[] + accountTokens: string[], + concurrency: number ): Promise { const maxCount = accountTokens.length; const spinner = ora().info(`Create Client Connection to ${url}`).start(); let i = 0; const sockets: Socket[] = []; - for (const token of accountTokens) { - await sleep(CLIENT_CREATION_INTERVAL_IN_MS); - spinner.text = `Progress: ${++i}/${maxCount}`; - const socket = await createClient(url, token); - sockets.push(socket); - } + await pMap( + accountTokens, + async (token) => { + await sleep(CLIENT_CREATION_INTERVAL_IN_MS); + const socket = await createClient(url, token); + spinner.text = `Progress: ${++i}/${maxCount}`; + sockets.push(socket); + }, + { + concurrency, + } + ); spinner.succeed(`${maxCount} clients has been create.`); @@ -131,7 +122,51 @@ function createClient(url: string, token: string): Promise { }); } -export function sleep(milliseconds: number): Promise { +async function sendMessage( + sockets: Socket[], + groupId: string, + converseId: string +) { + return new Promise((resolve) => { + const randomMessage = randomString({ length: 16 }); + const spinner = ora() + .info(`Start message receive test, message: ${randomMessage}`) + .start(); + const start = Date.now(); + let receiveCount = 0; + const len = sockets.length; + + function receivedCallback() { + receiveCount += 1; + spinner.text = `Receive: ${receiveCount}/${len}`; + + if (receiveCount === len) { + spinner.succeed(`All client received, usage: ${Date.now() - start}ms`); + resolve(); + } + } + + sockets.forEach((socket) => { + socket.on('notify:chat.message.add', (message) => { + const content = message.content; + + if (message.converseId === converseId && randomMessage === content) { + socket.off('notify:chat.message.add'); + + receivedCallback(); + } + }); + }); + + sockets[0].emit('chat.message.sendMessage', { + groupId, + converseId, + content: randomMessage, + }); + }); +} + +function sleep(milliseconds: number): Promise { return new Promise((resolve) => { setTimeout(resolve, milliseconds); }); diff --git a/apps/cli/src/commands/benchmark/register.ts b/apps/cli/src/commands/benchmark/register.ts index 2f622793..1f0d4b35 100644 --- a/apps/cli/src/commands/benchmark/register.ts +++ b/apps/cli/src/commands/benchmark/register.ts @@ -2,6 +2,7 @@ import { CommandModule } from 'yargs'; import fs from 'fs-extra'; import got from 'got'; import ora from 'ora'; +import pMap from 'p-map'; export const benchmarkRegisterCommand: CommandModule = { command: 'register ', @@ -25,33 +26,58 @@ export const benchmarkRegisterCommand: CommandModule = { type: 'number', default: 100, }) + .option('concurrency', { + describe: 'Concurrency when send request', + type: 'number', + default: 1, + }) .option('invite', { describe: 'Invite Code', type: 'string', + }) + .option('append', { + describe: 'Append mode', + type: 'boolean', }), async handler(args) { const url = args.url as string; + const file = args.file as string; const count = args.count as number; + const concurrency = args.concurrency as number; const invite = args.invite as string | undefined; + const append = (args.append ?? false) as boolean; const tokens: string[] = []; const start = Date.now(); const spinner = ora().info(`Register temporary account`).start(); - for (let i = 0; i < count; i++) { - spinner.text = `Progress: ${i + 1}/${count}`; + let i = 0; + spinner.text = `Progress: ${i}/${count}`; + await pMap( + Array.from({ length: count }), + async () => { + const token = await registerTemporaryAccount(url, `benchUser-${i}`); + if (invite) { + // Apply group invite + await applyGroupInviteCode(url, token, invite); + } + if (append) { + await fs.appendFile(file, `\n${token}`); + } - const token = await registerTemporaryAccount(url, `benchUser-${i}`); - if (invite) { - // Apply group invite - applyGroupInviteCode(url, token, invite); + spinner.text = `Progress: ${++i}/${count}`; + tokens.push(token); + }, + { + concurrency, } - tokens.push(token); - } + ); - spinner.info(`Writing tokens into path: ${args.file}`); + if (!append) { + spinner.info(`Writing tokens into path: ${file}`); - await fs.writeFile(args.file as string, tokens.join('\n')); + await fs.writeFile(file, tokens.join('\n')); + } spinner.succeed(`Register completed! Usage: ${Date.now() - start}ms`); }, @@ -66,6 +92,7 @@ async function registerTemporaryAccount( json: { nickname, }, + retry: 5, }) .json<{ data: { token: string } }>(); @@ -81,6 +108,7 @@ async function applyGroupInviteCode( json: { code: inviteCode, }, + retry: 5, headers: { 'X-Token': token, }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ad0a6b32..d0442ad3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -167,6 +167,9 @@ importers: p-all: specifier: 2.1.0 version: 2.1.0 + p-map: + specifier: ^4.0.0 + version: 4.0.0 p-series: specifier: 2.1.0 version: 2.1.0 @@ -568,7 +571,7 @@ importers: version: 0.32.11 zustand: specifier: ^4.3.6 - version: 4.3.6(immer@9.0.15)(react@18.2.0) + version: 4.3.6(immer@9.0.21)(react@18.2.0) devDependencies: '@types/crc': specifier: ^3.4.0 @@ -1836,7 +1839,7 @@ importers: version: 5.3.6(react-dom@18.2.0)(react-is@18.2.0)(react@18.2.0) zustand: specifier: ^4.3.6 - version: 4.3.6(immer@9.0.15)(react@18.2.0) + version: 4.3.6(immer@9.0.21)(react@18.2.0) server/plugins/com.msgbyte.getui: dependencies: @@ -2014,7 +2017,7 @@ importers: version: 5.3.6(react-dom@18.2.0)(react-is@18.2.0)(react@18.2.0) zustand: specifier: ^4.3.6 - version: 4.3.6(immer@9.0.15)(react@18.2.0) + version: 4.3.6(immer@9.0.21)(react@18.2.0) server/plugins/com.msgbyte.welcome: dependencies: @@ -21874,10 +21877,10 @@ packages: /immer@9.0.15: resolution: {integrity: sha512-2eB/sswms9AEUSkOm4SbV5Y7Vmt/bKRwByd52jfLkW4OLYeaTP3EEiJ9agqU0O/tq6Dk62Zfj+TJSqfm1rLVGQ==} + dev: false /immer@9.0.21: resolution: {integrity: sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==} - dev: false /import-fresh@2.0.0: resolution: {integrity: sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==} @@ -36599,6 +36602,7 @@ packages: immer: 9.0.15 react: 18.2.0 use-sync-external-store: 1.2.0(react@18.2.0) + dev: false /zustand@4.3.6(immer@9.0.21)(react@18.2.0): resolution: {integrity: sha512-6J5zDxjxLE+yukC2XZWf/IyWVKnXT9b9HUv09VJ/bwGCpKNcaTqp7Ws28Xr8jnbvnZcdRaidztAPsXFBIqufiw==} @@ -36615,7 +36619,6 @@ packages: immer: 9.0.21 react: 18.2.0 use-sync-external-store: 1.2.0(react@18.2.0) - dev: false /zwitch@1.0.5: resolution: {integrity: sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==}