[add] first

This commit is contained in:
2022-04-29 15:25:10 +08:00
commit da8024fc30
87 changed files with 16892 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
export const benchmarkConfig = {
/** 压测使用的APIServer */
server: 'http://127.0.0.1:3000',
/** 一共运行几次压测事务 */
total: 200000,
/** 同时并发的请求数量 */
concurrency: 100,
/** API请求的超时时间超时将断开HTTP连接释放资源前端默认为10 */
timeout: 10000,
/** 是否将错误的详情日志打印到Log */
showError: false
}

13
benchmark/http.ts Normal file
View File

@@ -0,0 +1,13 @@
import { benchmarkConfig } from './config/BenchmarkConfig';
import { HttpRunner } from './models/HTTPRunner';
const req = {
a: 123456,
b: 'Hello, World!',
c: true,
d: new Uint8Array(100000)
}
new HttpRunner(async function () {
await this.callApi('Test', req);
}, benchmarkConfig).start();

View File

@@ -0,0 +1,285 @@
import 'colors';
import * as http from "http";
import * as https from "https";
import 'k8w-extend-native';
import { TsrpcError, TsrpcErrorType } from "tsrpc-proto";
import { HttpClient } from '../../src/client/http/HttpClient';
import { benchmarkConfig } from "../config/BenchmarkConfig";
import { serviceProto } from '../protocols/proto';
export interface HttpRunnerConfig {
total: number;
concurrency: number;
showError?: boolean;
}
export class HttpRunner {
private _config: HttpRunnerConfig;
// 执行单个事务的方法
private _single: (this: HttpRunner) => Promise<void>;
// 执行进度信息
private _progress?: {
startTime: number,
lastSuccTime?: number,
started: number,
finished: number,
succ: number,
fail: number
};
constructor(single: HttpRunner['_single'], config: HttpRunnerConfig) {
this._single = single.bind(this);
this._config = config;
}
start() {
this._progress = {
startTime: Date.now(),
started: 0,
finished: 0,
succ: 0,
fail: 0
}
// 启动并发
for (let i = 0; i < this._config.concurrency; ++i) {
this._doTrans();
}
console.log('Benchmark start!');
this._startReport();
}
private _doTrans() {
if (this._isStoped || !this._progress) {
return;
}
if (this._progress.started < this._config.total) {
++this._progress.started;
let startTime = Date.now();
this._single().then(v => {
++this._progress!.succ;
this._progress!.lastSuccTime = Date.now();
}).catch(e => {
++this._progress!.fail;
if (this._config.showError) {
console.error('[Error]', e.message);
}
}).then(() => {
++this._progress!.finished;
if (this._progress!.finished === this._config.total) {
this._finish();
}
else {
this._doTrans();
}
})
}
}
private _reportInterval?: NodeJS.Timeout;
private _startReport() {
this._reportInterval = setInterval(() => {
this._report();
}, 1000)
}
private _isStoped = false;
stop() {
this._isStoped = true;
}
private _finish() {
if (!this._progress) {
return;
}
this._reportInterval && clearInterval(this._reportInterval);
console.log('\n\n-------------------------------\n Benchmark finished! \n-------------------------------');
let usedTime = Date.now() - this._progress.startTime;
console.log(` Transaction Execution Result `.bgBlue.white);
console.log(`Started=${this._progress.started}, Finished=${this._progress.finished}, UsedTime=${usedTime}ms`.green);
console.log(`Succ=${this._progress.succ}, Fail=${this._progress.fail}, TPS=${this._progress.succ / (this._progress.lastSuccTime! - this._progress.startTime) * 1000 | 0}\n`.green)
// TIME TPS(完成的)
console.log(` API Execution Result `.bgBlue.white);
// [KEY] RPS(完成的) AVG P95 P99
for (let key in this._apiStat) {
let stat = this._apiStat[key];
stat.resTime = stat.resTime.orderBy(v => v);
let send = stat.sendReq;
let succ = stat.resTime.length;
let netErr = stat.networkError;
let apiErr = stat.otherError;
let avg = stat.resTime[stat.resTime.length >> 1] | 0;
let p95 = stat.resTime[stat.resTime.length * 0.95 | 0] | 0;
let p99 = stat.resTime[stat.resTime.length * 0.99 | 0] | 0;
this._logTable([
[{ text: 'Api' + key + ' '.repeat(this._maxApiNameLength - key.length), color: 'green' }, 'Send', 'Succ', 'QPS', 'NetErr', 'ApiErr', 'AVG ', 'P95 ', 'P99 '],
['', '' + send,
{ text: '' + succ, color: 'green' },
{ text: '' + (succ / (stat.last.succTime - stat.startTime) * 1000 | 0), color: 'green' },
netErr ? { text: '' + netErr, color: 'red' } : '0',
apiErr ? { text: '' + apiErr, color: 'red' } : '0',
{ text: avg ? avg + 'ms' : '-', color: 'yellow' },
{ text: p95 ? p95 + 'ms' : '-', color: 'yellow' },
{ text: p99 ? p99 + 'ms' : '-', color: 'yellow' }
]
])
}
}
private _apiStat: {
[key: string]: {
sendReq: number,
resTime: number[],
succ: number,
networkError: number,
otherError: number,
startTime: number,
last: {
sendReq: number,
resTime: number[],
succ: number,
networkError: number,
otherError: number,
startTime: number,
succTime: number
}
}
} = {};
private _maxApiNameLength = 0;
/**
* callApi 并且计入统计
*/
callApi: typeof benchmarkClient.callApi = async (apiName, req) => {
this._maxApiNameLength = Math.max(apiName.length, this._maxApiNameLength);
if (!this._apiStat[apiName]) {
this._apiStat[apiName] = {
sendReq: 0,
resTime: [],
succ: 0,
networkError: 0,
otherError: 0,
startTime: Date.now(),
last: {
sendReq: 0,
resTime: [],
succ: 0,
networkError: 0,
otherError: 0,
startTime: Date.now(),
succTime: 0
}
};
}
++this._apiStat[apiName].sendReq;
++this._apiStat[apiName].last.sendReq;
let startTime = Date.now();
let ret = await benchmarkClient.callApi(apiName, req);
if (ret.isSucc) {
this._apiStat[apiName].last.succTime = Date.now();
this._apiStat[apiName].resTime.push(Date.now() - startTime);
this._apiStat[apiName].last.resTime.push(Date.now() - startTime);
++this._apiStat[apiName].succ;
++this._apiStat[apiName].last.succ;
}
else {
if (ret.err.type === TsrpcErrorType.NetworkError) {
++this._apiStat[apiName].networkError;
++this._apiStat[apiName].last.networkError;
}
else {
++this._apiStat[apiName].otherError;
++this._apiStat[apiName].last.otherError;
}
}
return ret;
}
private _report() {
console.log(new Date().format('hh:mm:ss').gray, `Started=${this._progress!.started}/${this._config.total}, Finished=${this._progress!.finished}, Succ=${this._progress!.succ.toString().green}, Fail=${this._progress!.fail.toString()[this._progress!.fail > 0 ? 'red' : 'white']}`,
this._progress!.lastSuccTime ? `TPS=${this._progress!.succ / (this._progress!.lastSuccTime - this._progress!.startTime) * 1000 | 0}` : '')
for (let key in this._apiStat) {
let stat = this._apiStat[key];
let send = stat.last.sendReq;
let succ = stat.last.resTime.length;
let netErr = stat.last.networkError;
let apiErr = stat.last.otherError;
this._logTable([
[{ text: 'Api' + key + ' '.repeat(this._maxApiNameLength - key.length), color: 'green' }, 'Send', 'Succ', 'QPS', 'NetErr', 'ApiErr'],
['', '' + send,
{ text: '' + succ, color: 'green' },
{ text: '' + (succ / (stat.last.succTime - stat.last.startTime) * 1000 | 0), color: 'green' },
netErr ? { text: '' + netErr, color: 'red' } : '0',
apiErr ? { text: '' + apiErr, color: 'red' } : '0'
]
])
Object.assign(stat.last, {
sendReq: 0,
resTime: [],
succ: 0,
networkError: 0,
otherError: 0,
startTime: Date.now(),
})
}
}
private _logTable(rows: [TableCellItem[], TableCellItem[]]) {
let cellWidths: number[] = [];
for (let cell of rows[0]) {
cellWidths.push(typeof cell === 'string' ? cell.length + 4 : cell.text.length + 4);
}
for (let row of rows) {
let line = '';
for (let i = 0; i < row.length; ++i) {
let cell = row[i];
let cellWidth = cellWidths[i];
if (typeof cell === 'string') {
line += cell + ' '.repeat(cellWidth - cell.length);
}
else {
line += cell.text[cell.color] + ' '.repeat(cellWidth - cell.text.length);
}
}
console.log(line);
}
}
}
export const benchmarkClient = new HttpClient(serviceProto, {
server: benchmarkConfig.server,
logger: {
debug: function () { },
log: function () { },
warn: function () { },
error: function () { },
},
timeout: benchmarkConfig.timeout,
agent: new (benchmarkConfig.server.startsWith('https') ? https : http).Agent({
keepAlive: true
})
})
type TableCellItem = (string | { text: string, color: 'green' | 'red' | 'yellow' });

View File

@@ -0,0 +1,283 @@
import assert from 'assert';
import 'colors';
import 'k8w-extend-native';
import { TsrpcErrorType } from "tsrpc-proto";
import { WsClient } from '../../src/client/ws/WsClient';
import { benchmarkConfig } from "../config/BenchmarkConfig";
import { serviceProto } from '../protocols/proto';
export interface WsRunnerConfig {
total: number;
concurrency: number;
showError?: boolean;
}
export class WsRunner {
private _config: WsRunnerConfig;
// 执行单个事务的方法
private _single: (this: WsRunner) => Promise<void>;
// 执行进度信息
private _progress?: {
startTime: number,
lastSuccTime?: number,
started: number,
finished: number,
succ: number,
fail: number
};
constructor(single: WsRunner['_single'], config: WsRunnerConfig) {
this._single = single.bind(this);
this._config = config;
}
async start() {
this._progress = {
startTime: Date.now(),
started: 0,
finished: 0,
succ: 0,
fail: 0
}
assert.ok(await benchmarkClient.connect(), 'Connect failed');
// 启动并发
for (let i = 0; i < this._config.concurrency; ++i) {
this._doTrans();
}
console.log('Benchmark start!');
this._startReport();
}
private _doTrans() {
if (this._isStoped || !this._progress) {
return;
}
if (this._progress.started < this._config.total) {
++this._progress.started;
let startTime = Date.now();
this._single().then(v => {
++this._progress!.succ;
this._progress!.lastSuccTime = Date.now();
}).catch(e => {
++this._progress!.fail;
if (this._config.showError) {
console.error('[Error]', e.message);
}
}).then(() => {
++this._progress!.finished;
if (this._progress!.finished === this._config.total) {
this._finish();
}
else {
this._doTrans();
}
})
}
}
private _reportInterval?: NodeJS.Timeout;
private _startReport() {
this._reportInterval = setInterval(() => {
this._report();
}, 1000)
}
private _isStoped = false;
stop() {
this._isStoped = true;
}
private _finish() {
if (!this._progress) {
return;
}
this._reportInterval && clearInterval(this._reportInterval);
console.log('\n\n-------------------------------\n Benchmark finished! \n-------------------------------');
let usedTime = Date.now() - this._progress.startTime;
console.log(` Transaction Execution Result `.bgBlue.white);
console.log(`Started=${this._progress.started}, Finished=${this._progress.finished}, UsedTime=${usedTime}ms`.green);
console.log(`Succ=${this._progress.succ}, Fail=${this._progress.fail}, TPS=${this._progress.succ / (this._progress.lastSuccTime! - this._progress.startTime) * 1000 | 0}\n`.green)
// TIME TPS(完成的)
console.log(` API Execution Result `.bgBlue.white);
// [KEY] RPS(完成的) AVG P95 P99
for (let key in this._apiStat) {
let stat = this._apiStat[key];
stat.resTime = stat.resTime.orderBy(v => v);
let send = stat.sendReq;
let succ = stat.resTime.length;
let netErr = stat.networkError;
let apiErr = stat.otherError;
let avg = stat.resTime[stat.resTime.length >> 1] | 0;
let p95 = stat.resTime[stat.resTime.length * 0.95 | 0] | 0;
let p99 = stat.resTime[stat.resTime.length * 0.99 | 0] | 0;
this._logTable([
[{ text: 'Api' + key + ' '.repeat(this._maxApiNameLength - key.length), color: 'green' }, 'Send', 'Succ', 'QPS', 'NetErr', 'ApiErr', 'AVG ', 'P95 ', 'P99 '],
['', '' + send,
{ text: '' + succ, color: 'green' },
{ text: '' + (succ / (stat.last.succTime - stat.startTime) * 1000 | 0), color: 'green' },
netErr ? { text: '' + netErr, color: 'red' } : '0',
apiErr ? { text: '' + apiErr, color: 'red' } : '0',
{ text: avg ? avg + 'ms' : '-', color: 'yellow' },
{ text: p95 ? p95 + 'ms' : '-', color: 'yellow' },
{ text: p99 ? p99 + 'ms' : '-', color: 'yellow' }
]
])
}
}
private _apiStat: {
[key: string]: {
sendReq: number,
resTime: number[],
succ: number,
networkError: number,
otherError: number,
startTime: number,
last: {
sendReq: number,
resTime: number[],
succ: number,
networkError: number,
otherError: number,
startTime: number,
succTime: number
}
}
} = {};
private _maxApiNameLength = 0;
/**
* callApi 并且计入统计
*/
callApi: typeof benchmarkClient.callApi = async (apiName, req) => {
this._maxApiNameLength = Math.max(apiName.length, this._maxApiNameLength);
if (!this._apiStat[apiName]) {
this._apiStat[apiName] = {
sendReq: 0,
resTime: [],
succ: 0,
networkError: 0,
otherError: 0,
startTime: Date.now(),
last: {
sendReq: 0,
resTime: [],
succ: 0,
networkError: 0,
otherError: 0,
startTime: Date.now(),
succTime: 0
}
};
}
++this._apiStat[apiName].sendReq;
++this._apiStat[apiName].last.sendReq;
let startTime = Date.now();
let ret = await benchmarkClient.callApi(apiName, req);
if (ret.isSucc) {
this._apiStat[apiName].last.succTime = Date.now();
this._apiStat[apiName].resTime.push(Date.now() - startTime);
this._apiStat[apiName].last.resTime.push(Date.now() - startTime);
++this._apiStat[apiName].succ;
++this._apiStat[apiName].last.succ;
}
else {
if (ret.err.type === TsrpcErrorType.NetworkError) {
++this._apiStat[apiName].networkError;
++this._apiStat[apiName].last.networkError;
}
else {
++this._apiStat[apiName].otherError;
++this._apiStat[apiName].last.otherError;
}
}
return ret;
}
private _report() {
console.log(new Date().format('hh:mm:ss').gray, `Started=${this._progress!.started}/${this._config.total}, Finished=${this._progress!.finished}, Succ=${this._progress!.succ.toString().green}, Fail=${this._progress!.fail.toString()[this._progress!.fail > 0 ? 'red' : 'white']}`,
this._progress!.lastSuccTime ? `TPS=${this._progress!.succ / (this._progress!.lastSuccTime - this._progress!.startTime) * 1000 | 0}` : '')
for (let key in this._apiStat) {
let stat = this._apiStat[key];
let send = stat.last.sendReq;
let succ = stat.last.resTime.length;
let netErr = stat.last.networkError;
let apiErr = stat.last.otherError;
this._logTable([
[{ text: 'Api' + key + ' '.repeat(this._maxApiNameLength - key.length), color: 'green' }, 'Send', 'Succ', 'QPS', 'NetErr', 'ApiErr'],
['', '' + send,
{ text: '' + succ, color: 'green' },
{ text: '' + (succ / (stat.last.succTime - stat.last.startTime) * 1000 | 0), color: 'green' },
netErr ? { text: '' + netErr, color: 'red' } : '0',
apiErr ? { text: '' + apiErr, color: 'red' } : '0'
]
])
Object.assign(stat.last, {
sendReq: 0,
resTime: [],
succ: 0,
networkError: 0,
otherError: 0,
startTime: Date.now(),
})
}
}
private _logTable(rows: [TableCellItem[], TableCellItem[]]) {
let cellWidths: number[] = [];
for (let cell of rows[0]) {
cellWidths.push(typeof cell === 'string' ? cell.length + 4 : cell.text.length + 4);
}
for (let row of rows) {
let line = '';
for (let i = 0; i < row.length; ++i) {
let cell = row[i];
let cellWidth = cellWidths[i];
if (typeof cell === 'string') {
line += cell + ' '.repeat(cellWidth - cell.length);
}
else {
line += cell.text[cell.color] + ' '.repeat(cellWidth - cell.text.length);
}
}
console.log(line);
}
}
}
export const benchmarkClient = new WsClient(serviceProto, {
server: benchmarkConfig.server,
logger: {
debug: function () { },
log: function () { },
warn: function () { },
error: function () { },
},
timeout: benchmarkConfig.timeout
})
type TableCellItem = (string | { text: string, color: 'green' | 'red' | 'yellow' });

View File

@@ -0,0 +1,14 @@
import { uint } from 'tsbuffer-schema';
export interface ReqTest {
a?: uint;
b?: string;
c?: boolean;
d?: Uint8Array;
}
export interface ResTest {
a?: uint;
b?: string;
c?: boolean;
d?: Uint8Array;
}

View File

@@ -0,0 +1,104 @@
import { ServiceProto } from 'tsrpc-proto';
import { ReqTest, ResTest } from './PtlTest'
export interface ServiceType {
api: {
"Test": {
req: ReqTest,
res: ResTest
}
},
msg: {
}
}
export const serviceProto: ServiceProto<ServiceType> = {
"services": [
{
"id": 0,
"name": "Test",
"type": "api"
}
],
"types": {
"PtlTest/ReqTest": {
"type": "Interface",
"properties": [
{
"id": 0,
"name": "a",
"type": {
"type": "Number",
"scalarType": "uint"
},
"optional": true
},
{
"id": 1,
"name": "b",
"type": {
"type": "String"
},
"optional": true
},
{
"id": 2,
"name": "c",
"type": {
"type": "Boolean"
},
"optional": true
},
{
"id": 3,
"name": "d",
"type": {
"type": "Buffer",
"arrayType": "Uint8Array"
},
"optional": true
}
]
},
"PtlTest/ResTest": {
"type": "Interface",
"properties": [
{
"id": 0,
"name": "a",
"type": {
"type": "Number",
"scalarType": "uint"
},
"optional": true
},
{
"id": 1,
"name": "b",
"type": {
"type": "String"
},
"optional": true
},
{
"id": 2,
"name": "c",
"type": {
"type": "Boolean"
},
"optional": true
},
{
"id": 3,
"name": "d",
"type": {
"type": "Buffer",
"arrayType": "Uint8Array"
},
"optional": true
}
]
}
}
};

26
benchmark/server/http.ts Normal file
View File

@@ -0,0 +1,26 @@
import { HttpServer } from '../../src/index';
import { serviceProto } from "../protocols/proto";
async function main() {
let server = new HttpServer(serviceProto, {
logger: {
debug: () => { },
log: () => { },
error: console.error.bind(console),
warn: console.warn.bind(console)
}
});
server.implementApi('Test', call => {
call.succ(call.req);
});
await server.start();
setInterval(() => {
let used = process.memoryUsage().heapUsed / 1024 / 1024;
console.log(`内存: ${Math.round(used * 100) / 100} MB`);
}, 2000)
}
main();

26
benchmark/server/ws.ts Normal file
View File

@@ -0,0 +1,26 @@
import { WsServer } from '../../src/index';
import { serviceProto } from "../protocols/proto";
async function main() {
let server = new WsServer(serviceProto, {
logger: {
debug: () => { },
log: () => { },
error: console.error.bind(console),
warn: console.warn.bind(console)
}
});
server.implementApi('Test', call => {
call.succ(call.req);
});
await server.start();
setInterval(() => {
let used = process.memoryUsage().heapUsed / 1024 / 1024;
console.log(`内存: ${Math.round(used * 100) / 100} MB`);
}, 2000)
}
main();

63
benchmark/tsconfig.json Normal file
View File

@@ -0,0 +1,63 @@
{
"compilerOptions": {
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
// "outDir": "./", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
}
}

13
benchmark/ws.ts Normal file
View File

@@ -0,0 +1,13 @@
import { benchmarkConfig } from './config/BenchmarkConfig';
import { WsRunner } from './models/WsRunner';
const req = {
a: 123456,
b: 'Hello, World!',
c: true,
d: new Uint8Array(100000)
}
new WsRunner(async function () {
await this.callApi('Test', req);
}, benchmarkConfig).start();