TSRPC_Test/benchmark/models/WsRunner.ts
2022-04-29 15:25:10 +08:00

283 lines
9.4 KiB
TypeScript

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' });