From 6ec498cc6fdedb350dad1cb473e0c33d07b3b024 Mon Sep 17 00:00:00 2001 From: IdanILT Date: Mon, 17 Apr 2023 17:42:28 +0300 Subject: [PATCH 1/4] feat: added package for benchmark --- packages/benchmark/.gitignore | 6 ++ packages/benchmark/README.md | 91 ++++++++++++++++++++ packages/benchmark/benchmark.json | 28 +++++++ packages/benchmark/index.js | 4 + packages/benchmark/jest.config.js | 9 ++ packages/benchmark/package.json | 33 ++++++++ packages/benchmark/rollup.cjs.config.js | 35 ++++++++ packages/benchmark/src/collect.ts | 35 ++++++++ packages/benchmark/src/index.ts | 48 +++++++++++ packages/benchmark/src/reporters/html.ts | 75 +++++++++++++++++ packages/benchmark/src/reporters/index.ts | 2 + packages/benchmark/src/reporters/json.ts | 11 +++ packages/benchmark/src/util.ts | 97 ++++++++++++++++++++++ packages/benchmark/tests/benchmark.spec.ts | 8 ++ packages/benchmark/tsconfig.json | 15 ++++ 15 files changed, 497 insertions(+) create mode 100644 packages/benchmark/.gitignore create mode 100644 packages/benchmark/README.md create mode 100644 packages/benchmark/benchmark.json create mode 100755 packages/benchmark/index.js create mode 100644 packages/benchmark/jest.config.js create mode 100644 packages/benchmark/package.json create mode 100644 packages/benchmark/rollup.cjs.config.js create mode 100644 packages/benchmark/src/collect.ts create mode 100644 packages/benchmark/src/index.ts create mode 100644 packages/benchmark/src/reporters/html.ts create mode 100644 packages/benchmark/src/reporters/index.ts create mode 100644 packages/benchmark/src/reporters/json.ts create mode 100644 packages/benchmark/src/util.ts create mode 100644 packages/benchmark/tests/benchmark.spec.ts create mode 100644 packages/benchmark/tsconfig.json diff --git a/packages/benchmark/.gitignore b/packages/benchmark/.gitignore new file mode 100644 index 000000000..504abe0d2 --- /dev/null +++ b/packages/benchmark/.gitignore @@ -0,0 +1,6 @@ +.cache +node_modules + +lib/ + +report.cjs.html diff --git a/packages/benchmark/README.md b/packages/benchmark/README.md new file mode 100644 index 000000000..28593495f --- /dev/null +++ b/packages/benchmark/README.md @@ -0,0 +1,91 @@ +[![Join the chat at https://gitter.im/scalecube-js/Lobby](https://badges.gitter.im/scalecube-js/Lobby.svg)](https://gitter.im/scalecube-js/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +> This is part of [scalecube-js](https://github.com/scalecube/scalecube-js) project, see more at +> [Full documentation](https://scalecube.github.io/javascript-docs) + +# Microservices-browser + +This package provides Scalecube's solution with default setting for working in node. + +### Usage + +`yarn add @scalecube/node` or `npm i @scalecube/node` + +```typescript +import { createMicroservice, ASYNC_MODEL_TYPES } from '@scalecube/node'; +``` + +**create a seed** + +```typescript +export const MySeedAddress: 'ws://localhost:8000'; + +// Create a service +createMicroservice({ + address : MySeedAddress +}); +``` + +**Create a service** + +```typescript +// Create service definition +export const greetingServiceDefinition = { + serviceName: 'GreetingService', + methods: { + hello: { + asyncModel: ASYNC_MODEL_TYPES.REQUEST_RESPONSE, + } + }, +}; +// Create a service +createMicroservice({ + service : [{ + definition: greetingServiceDefinition, + reference: { + hello : (name) => `Hello ${name}` + }, + }], + seedAddress : MySeedAddress, + address : 'ws://localhost:8001' +}); +``` + +**Use a service** + +```typescript +const microservice = createMicroservice({ + seedAddress : MySeedAddress, + address : 'ws://localhost:8002' +}) + +// With proxy +const greetingService = microservice.createProxy({ + serviceDefinition: greetingServiceDefinition +}); + +greetingService.hello('ME').then(console.log) // Hello ME +``` + +**Dependency Injection** + +```typescript +createMicroservice({ + seedAddress : MySeedAddress, + address : 'ws://localhost:8003', + services: [ + { + definition: serviceB, + reference: ({ createProxy, createServiceCall }) => { + const greetingService = createProxy({serviceDefinition: greetingServiceDefinition }); + + return new ServiceB(greetingService); + } + } + ] +}) +``` + +## documentation + +please [Read](https://scalecube.github.io/javascript-docs) before starting to work with scalecube. diff --git a/packages/benchmark/benchmark.json b/packages/benchmark/benchmark.json new file mode 100644 index 000000000..422c24e20 --- /dev/null +++ b/packages/benchmark/benchmark.json @@ -0,0 +1,28 @@ +{ + "name": "benchmark name", + "server": [ + { + "start": "echo start", + "warmup": "echo warm", + "stop": "echo stop" + } + ], + "benchmark": [ + { + "name": "variant 1", + "client": { + "command": "echo '{\"1\": 5}'", + "repeat": 3, + "delay": 0 + } + }, + { + "name": "variant 2", + "client": { + "command": "echo '{\"3\": 5}'", + "repeat": 3, + "delay": 0 + } + } + ] +} diff --git a/packages/benchmark/index.js b/packages/benchmark/index.js new file mode 100755 index 000000000..0b717ff52 --- /dev/null +++ b/packages/benchmark/index.js @@ -0,0 +1,4 @@ +#!/usr/bin/env node + +const { run } = require('./lib/index'); +run(process.argv[2]).then(() => console.log('done')); diff --git a/packages/benchmark/jest.config.js b/packages/benchmark/jest.config.js new file mode 100644 index 000000000..46a7ad460 --- /dev/null +++ b/packages/benchmark/jest.config.js @@ -0,0 +1,9 @@ +module.exports = { + transform: { + '.(ts|tsx)': 'ts-jest', + }, + testRegex: '(\\.|/)spec\\.ts$', + testPathIgnorePatterns: ['/es/', '/lib/', '/node_modules/'], + moduleFileExtensions: ['ts', 'tsx', 'js', 'node'], + moduleDirectories: ['node_modules', '/src', '/node_modules/'], +}; diff --git a/packages/benchmark/package.json b/packages/benchmark/package.json new file mode 100644 index 000000000..6b310bd5b --- /dev/null +++ b/packages/benchmark/package.json @@ -0,0 +1,33 @@ +{ + "name": "@scalecube/benchmark", + "version": "0.2.11", + "private": false, + "main": "lib/index.js", + "types": "lib/index.d.ts", + "files": [ + "lib" + ], + "license": "MIT", + "bin": { + "benchmark": "./index.js" + }, + "scripts": { + "clean": "rimraf node_modules && rimraf .cache && rimraf lib", + "build": "rimraf .cache && yarn build:cjs", + "build:cjs": "rimraf lib && rollup -c rollup.cjs.config.js", + "lint": "tslint '{src,tests}/**/*.{ts,tsx}' --fix", + "prettier": "prettier --write '{src,tests}/**/*.{ts,tsx}'", + "test": "jest --config jest.config.js --forceExit" + }, + "author": "Scalecube (https://github.com/scalecube/scalecube-js)", + "devDependencies": { + "rollup": "^1.27.4", + "rollup-plugin-babel": "^4.3.3", + "rollup-plugin-filesize": "^6.1.1", + "rollup-plugin-typescript2": "^0.21.1", + "rollup-plugin-visualizer": "^2.6.0", + "tslint": "^5.11.0", + "typescript": "^3.2.4" + }, + "dependencies": {} +} diff --git a/packages/benchmark/rollup.cjs.config.js b/packages/benchmark/rollup.cjs.config.js new file mode 100644 index 000000000..eb2578921 --- /dev/null +++ b/packages/benchmark/rollup.cjs.config.js @@ -0,0 +1,35 @@ +import visualizer from 'rollup-plugin-visualizer'; +import typescript from 'rollup-plugin-typescript2'; +import tscompile from 'typescript'; +import filesize from 'rollup-plugin-filesize'; +import commonjs from 'rollup-plugin-commonjs'; +import pkg from './package.json'; + +export default { + input: 'src/index.ts', + output: [ + { + file: pkg.main, + format: 'cjs', + sourcemap: false, + }, + ], + external: ['rxjs'], + plugins: [ + commonjs({ + include: /node_modules/, + namedExports: { + 'rsocket-types': ['CONNECTION_STATUS'], + }, + }), + visualizer({ + filename: 'report.cjs.html', + title: 'Node - cjs', + }), + typescript({ + typescript: tscompile, + clean: true, + }), + filesize(), + ], +}; diff --git a/packages/benchmark/src/collect.ts b/packages/benchmark/src/collect.ts new file mode 100644 index 000000000..179d528f1 --- /dev/null +++ b/packages/benchmark/src/collect.ts @@ -0,0 +1,35 @@ +import { Buckets } from './index'; + +export async function collect(buckets: Promise, name: string) { + const data = await buckets; + const merged: any = {}; + let total = 0; + let mean = 0; + let sum = 0; + let stdev = 0; + data.forEach((str) => { + const obj: Buckets = JSON.parse(str); + Object.keys(obj).forEach((c) => { + const ms = Number(c); + merged[c] = { x: ms, value: (merged?.[c]?.value || 0) + obj[c] }; + for (let i = 0; i < obj[c]; i++) { + total += 1; + const delta = ms - mean; + mean += delta / total; + const delta2 = ms - mean; + sum += delta * delta2; + } + }); + }); + if (total > 1) { + stdev = sum / total; + } + + return { + name, + total, + mean, + stdev, + data: Object.values(merged), + }; +} diff --git a/packages/benchmark/src/index.ts b/packages/benchmark/src/index.ts new file mode 100644 index 000000000..72f8ef677 --- /dev/null +++ b/packages/benchmark/src/index.ts @@ -0,0 +1,48 @@ +import { getConf, showHelp, exec, print, execDelayed } from './util'; +import * as reporters from 'reporters'; +import { collect } from './collect'; + +export interface Buckets { + [ms: string]: number; +} +function report(data: any[], name: string) { + reporters.json(data, name); + reporters.html(data, name); +} + +export async function run(path: string) { + const conf = getConf(path); + + if (!conf) { + showHelp(); + return; + } + const servers = conf.server || []; + const benchmarks = conf.benchmark || []; + const parallel = servers.length > 0 ? servers.length : 1; + const running_servers = []; + + for (const server of servers) { + running_servers.push(exec(server.start).then(() => print('server is closed'))); + if (server.warmup) { + await exec(server.warmup); + } + } + const result = []; + const running = 0; + while (benchmarks.length) { + if (running === parallel) { + await Promise.race(result); + } + const setting = benchmarks.pop(); + const p = []; + for (let i = 0; i < setting.client.repeat; i++) { + p.push(execDelayed(setting.client.command, i * setting.client.delay)); + } + result.push(collect(Promise.all(p) as Promise, setting.name)); + } + + report((await Promise.all(result)) as any, conf.name); + + // running_servers.forEach(p => p.c) +} diff --git a/packages/benchmark/src/reporters/html.ts b/packages/benchmark/src/reporters/html.ts new file mode 100644 index 000000000..6ae2f23f8 --- /dev/null +++ b/packages/benchmark/src/reporters/html.ts @@ -0,0 +1,75 @@ +import fs from 'fs'; +import { Buckets } from '../index'; +export function html(data: any[], name: string) { + const report = { + name, + series: data.map((s) => ({ + data: s.data, + name: `${s.name}, total: ${s.total}, mean: ${s.mean}, stdev: ${s.stdev}`, + })), + }; + const f = tmpl(JSON.stringify(report)); + fs.writeFileSync(name.replace(/[^a-z0-9]/gi, '_').toLowerCase() + '.html', f, { encoding: 'utf8' }); +} + +const tmpl = (data: string) => ` +
+ + + + + +
+ +
+`; diff --git a/packages/benchmark/src/reporters/index.ts b/packages/benchmark/src/reporters/index.ts new file mode 100644 index 000000000..0669e6088 --- /dev/null +++ b/packages/benchmark/src/reporters/index.ts @@ -0,0 +1,2 @@ +export { json } from './json'; +export { html } from './html'; diff --git a/packages/benchmark/src/reporters/json.ts b/packages/benchmark/src/reporters/json.ts new file mode 100644 index 000000000..75975a7ba --- /dev/null +++ b/packages/benchmark/src/reporters/json.ts @@ -0,0 +1,11 @@ +import fs from 'fs'; +import { Buckets } from '../index'; +export function json(data: any[], name: string) { + const report = { + name, + series: data, + }; + fs.writeFileSync(name.replace(/[^a-z0-9]/gi, '_').toLowerCase() + '.json', JSON.stringify(report), { + encoding: 'utf8', + }); +} diff --git a/packages/benchmark/src/util.ts b/packages/benchmark/src/util.ts new file mode 100644 index 000000000..636b2fbda --- /dev/null +++ b/packages/benchmark/src/util.ts @@ -0,0 +1,97 @@ +import { exec as _exec } from 'child_process'; +import fs from 'fs'; + +export function print(...args: any[]) { + // tslint:disable-next-line:no-console + console.log(...args); +} + +export function exec(cmd: string) { + return new Promise((resolve, reject) => { + _exec(cmd, (error, stdout, stderr) => { + if (error) { + return reject(error); + } + if (stderr) { + return reject(stderr); + } + resolve(stdout); + }); + }); +} + +export function execDelayed(cmd: string, delay: number) { + return new Promise((resolve) => { + setTimeout(async () => { + resolve(await exec(cmd)); + }, delay); + }); +} + +export function getConf(f = 'benchmark.json') { + let file: any; + try { + file = fs.readFileSync(f, { encoding: 'utf8' }); + } catch (e) { + print('file not found'); + return; + } + const conf = JSON.parse(file); + const { name, benchmark, server } = conf; + + if (!name || !benchmark) { + configHelp(); + return; + } + + benchmark.forEach((variant: any) => { + if (!variant?.name || !variant?.client?.command) { + print('client must have name and command'); + configHelp(); + return; + } + variant.client.repeat = variant?.client?.repeat || 1; + variant.client.delay = variant?.client?.delay || 1; + }); + + server.forEach((s: any) => { + if (!s?.start) { + print('server must have start'); + configHelp(); + return; + } + }); + + return conf; +} + +export function configHelp() { + print(`{ + "name": "required benchmark name", + "server": [{ + "start": "required docker ... | any cmd to start server", + "warmup": "opt cmd to warm the server", + "stop": "opt command to stop server" + }], + "benchmark": [{ + "name": "required variant 1", + "client": { + "command": "required docker --rm client:tag | any bash command", + "repeat": 1000, + "delay": 1 + } + }] +}`); +} + +export function showHelp() { + print(` +Usage: benchmark [file.json=benchmark.json] + Run server, run warm commands and generate traffic (you need to supply clients), collect data and create reports + The client should return the data in buckets in json format: + { + "1": 5, + } + When the key is MS and value is count of how many request returned in 1 MS +`); +} diff --git a/packages/benchmark/tests/benchmark.spec.ts b/packages/benchmark/tests/benchmark.spec.ts new file mode 100644 index 000000000..88d492fee --- /dev/null +++ b/packages/benchmark/tests/benchmark.spec.ts @@ -0,0 +1,8 @@ +import { run } from '../src'; +import * as process from 'process'; + +describe('benchmark', () => { + it('run', async () => { + await run('benchmark.json'); + }); +}); diff --git a/packages/benchmark/tsconfig.json b/packages/benchmark/tsconfig.json new file mode 100644 index 000000000..dde84113b --- /dev/null +++ b/packages/benchmark/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "es6", + "lib": ["dom", "es2015", "es7", "es5", "es6", "ES2019"], + "declaration": true, + "outDir": "./lib", + "strict": true, + "moduleResolution": "node", + "baseUrl": "./src", + "esModuleInterop": true + }, + "include": ["src/**/*", "../../node_modules/@types/shelljs/make.d.ts"], + "exclude": ["node_modules", "**/*.spec.*"] +} From 5615151b0208cb16f4d943e6ae8034e0717dd0d1 Mon Sep 17 00:00:00 2001 From: IdanILT Date: Mon, 17 Apr 2023 17:49:39 +0300 Subject: [PATCH 2/4] lint --- packages/benchmark/src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/benchmark/src/index.ts b/packages/benchmark/src/index.ts index 72f8ef677..730715626 100644 --- a/packages/benchmark/src/index.ts +++ b/packages/benchmark/src/index.ts @@ -20,10 +20,10 @@ export async function run(path: string) { const servers = conf.server || []; const benchmarks = conf.benchmark || []; const parallel = servers.length > 0 ? servers.length : 1; - const running_servers = []; + const runningServers = []; for (const server of servers) { - running_servers.push(exec(server.start).then(() => print('server is closed'))); + runningServers.push(exec(server.start).then(() => print('server is closed'))); if (server.warmup) { await exec(server.warmup); } From 84006956b0f4916a1fc93b439dcf6429215d7073 Mon Sep 17 00:00:00 2001 From: Idan Levin Date: Mon, 17 Apr 2023 21:50:33 +0300 Subject: [PATCH 3/4] publish with node 14 --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f08b54fca..232937bef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,10 +36,10 @@ jobs: - name: test run: yarn test - name: doc - if: ${{ matrix.node == 16 }} + if: ${{ matrix.node == 14 }} run: yarn doc - name: publish npm - if: ${{ matrix.node == 16 }} + if: ${{ matrix.node == 14 }} run: bash scripts/publish.sh env: SHA: ${{ github.event.after }} From 643023c88a7ff8cc1087a41d7540bd1d6b59992c Mon Sep 17 00:00:00 2001 From: Idan Levin Date: Mon, 17 Apr 2023 21:56:29 +0300 Subject: [PATCH 4/4] Update ci.yml --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 232937bef..486f362d8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: - name: test run: yarn test - name: doc - if: ${{ matrix.node == 14 }} + if: ${{ matrix.node == 16 }} run: yarn doc - name: publish npm if: ${{ matrix.node == 14 }}