2020-12-31 04:13:19 +08:00
|
|
|
import {BenchmarkResult} from './BenchmarkRunner';
|
|
|
|
import {ContentWrapper} from '../src/common/entities/ConentWrapper';
|
2021-04-18 21:48:35 +08:00
|
|
|
import {Express, NextFunction} from 'express';
|
2020-12-31 19:35:28 +08:00
|
|
|
import {Utils} from '../src/common/Utils';
|
|
|
|
import {Message} from '../src/common/entities/Message';
|
2021-05-13 15:56:36 +08:00
|
|
|
import {ActiveExperiments, Experiments} from './Experiments';
|
2020-12-31 04:13:19 +08:00
|
|
|
|
|
|
|
export interface BenchmarkStep {
|
|
|
|
name: string;
|
|
|
|
fn: ((input?: any) => Promise<ContentWrapper | any[] | void>);
|
|
|
|
}
|
|
|
|
|
2020-12-31 19:35:28 +08:00
|
|
|
/**
|
|
|
|
* This class converts PiGallery2 Routers to benchamrkable steps to the Benchmark class
|
|
|
|
*/
|
|
|
|
class BMExpressApp {
|
|
|
|
readonly benchmark: Benchmark;
|
|
|
|
|
|
|
|
|
|
|
|
constructor(benchmark: Benchmark) {
|
|
|
|
this.benchmark = benchmark;
|
|
|
|
}
|
|
|
|
|
2021-04-18 21:48:35 +08:00
|
|
|
get(match: string | string[], ...functions: ((req: any, res: any, next: NextFunction) => void)[]): void {
|
|
|
|
functions.forEach((f): void => {
|
2020-12-31 19:35:28 +08:00
|
|
|
this.benchmark.addAStep({
|
|
|
|
name: this.camelToSpaceSeparated(f.name),
|
2021-04-18 21:48:35 +08:00
|
|
|
fn: (request: any): Promise<void> => this.nextToPromise(f, request)
|
2020-12-31 19:35:28 +08:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-04-18 21:48:35 +08:00
|
|
|
private camelToSpaceSeparated(text: string): string {
|
2020-12-31 19:35:28 +08:00
|
|
|
const result = (text.replace(/([A-Z])/g, ' $1')).toLocaleLowerCase();
|
|
|
|
return result.charAt(0).toUpperCase() + result.slice(1);
|
|
|
|
}
|
|
|
|
|
2021-04-18 21:48:35 +08:00
|
|
|
private nextToPromise(fn: (req: any, res: any, next: NextFunction) => void, request: any): Promise<void> {
|
|
|
|
return new Promise<void>((resolve, reject): void => {
|
2020-12-31 19:35:28 +08:00
|
|
|
const response = {
|
2021-04-18 21:48:35 +08:00
|
|
|
header: (): void => {
|
2020-12-31 19:35:28 +08:00
|
|
|
},
|
2021-04-18 21:48:35 +08:00
|
|
|
json: (data: any): void => {
|
2020-12-31 19:35:28 +08:00
|
|
|
resolve(data);
|
|
|
|
}
|
|
|
|
};
|
2021-04-18 21:48:35 +08:00
|
|
|
fn(request, response, (err?: any): void => {
|
2020-12-31 19:35:28 +08:00
|
|
|
if (err) {
|
|
|
|
return reject(err);
|
|
|
|
}
|
|
|
|
resolve(request.resultPipe);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-31 04:13:19 +08:00
|
|
|
export class Benchmark {
|
|
|
|
|
|
|
|
|
|
|
|
steps: BenchmarkStep[] = [];
|
|
|
|
name: string;
|
2020-12-31 19:35:28 +08:00
|
|
|
request: any;
|
2020-12-31 04:13:19 +08:00
|
|
|
beforeEach: () => Promise<any>;
|
|
|
|
afterEach: () => Promise<any>;
|
2022-02-23 06:50:28 +08:00
|
|
|
beforeAll: () => Promise<any>;
|
|
|
|
afterAll: () => Promise<any>;
|
2020-12-31 19:35:28 +08:00
|
|
|
private readonly bmExpressApp: BMExpressApp;
|
2020-12-31 04:13:19 +08:00
|
|
|
|
|
|
|
|
|
|
|
constructor(name: string,
|
2020-12-31 19:35:28 +08:00
|
|
|
request: any = {},
|
2020-12-31 04:13:19 +08:00
|
|
|
beforeEach?: () => Promise<any>,
|
2022-02-23 06:50:28 +08:00
|
|
|
afterEach?: () => Promise<any>,
|
|
|
|
beforeAll?: () => Promise<any>,
|
|
|
|
afterAll?: () => Promise<any>) {
|
2020-12-31 04:13:19 +08:00
|
|
|
this.name = name;
|
2020-12-31 19:35:28 +08:00
|
|
|
this.request = request;
|
2020-12-31 04:13:19 +08:00
|
|
|
this.beforeEach = beforeEach;
|
|
|
|
this.afterEach = afterEach;
|
2022-02-23 06:50:28 +08:00
|
|
|
this.beforeAll = beforeAll;
|
|
|
|
this.afterAll = afterAll;
|
2020-12-31 19:35:28 +08:00
|
|
|
this.bmExpressApp = new BMExpressApp(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
get BmExpressApp(): Express {
|
2021-04-18 21:48:35 +08:00
|
|
|
return (this.bmExpressApp as unknown) as Express;
|
2020-12-31 04:13:19 +08:00
|
|
|
}
|
|
|
|
|
2021-05-13 15:56:36 +08:00
|
|
|
async run(RUNS: number): Promise<BenchmarkResult[]> {
|
2022-02-23 07:05:49 +08:00
|
|
|
const ret: BenchmarkResult[] = [];
|
|
|
|
const r = async (): Promise<void> => {
|
|
|
|
if (this.beforeAll) {
|
|
|
|
await this.beforeAll();
|
|
|
|
}
|
|
|
|
ret.push(await this.runAnExperiment(RUNS));
|
|
|
|
if (this.afterAll) {
|
|
|
|
await this.afterAll();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
await r();
|
2021-05-13 15:56:36 +08:00
|
|
|
for (const exp of Object.values(Experiments)) {
|
|
|
|
for (const group of Object.values(exp.groups)) {
|
|
|
|
ActiveExperiments[exp.name] = group;
|
2022-02-23 07:05:49 +08:00
|
|
|
await r();
|
2021-05-13 15:56:36 +08:00
|
|
|
ret[ret.length - 1].experiment = exp.name + '=' + group;
|
|
|
|
}
|
|
|
|
delete ActiveExperiments[exp.name];
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
async runAnExperiment(RUNS: number): Promise<BenchmarkResult> {
|
2020-12-31 04:13:19 +08:00
|
|
|
console.log('Running benchmark: ' + this.name);
|
|
|
|
const scanned = await this.scanSteps();
|
|
|
|
const start = process.hrtime();
|
|
|
|
let skip = 0;
|
|
|
|
const stepTimer = new Array(this.steps.length).fill(0);
|
|
|
|
for (let i = 0; i < RUNS; i++) {
|
|
|
|
if (this.beforeEach) {
|
|
|
|
const startSkip = process.hrtime();
|
|
|
|
await this.beforeEach();
|
|
|
|
const endSkip = process.hrtime(startSkip);
|
|
|
|
skip += (endSkip[0] * 1000 + endSkip[1] / 1000000);
|
|
|
|
}
|
|
|
|
await this.runOneRound(stepTimer);
|
|
|
|
if (this.afterEach) {
|
|
|
|
const startSkip = process.hrtime();
|
|
|
|
await this.afterEach();
|
|
|
|
const endSkip = process.hrtime(startSkip);
|
|
|
|
skip += (endSkip[0] * 1000 + endSkip[1] / 1000000);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const end = process.hrtime(start);
|
|
|
|
const duration = (end[0] * 1000 + end[1] / 1000000 - skip) / RUNS;
|
|
|
|
|
|
|
|
|
|
|
|
const ret = this.outputToBMResult(this.name, scanned[scanned.length - 1]);
|
|
|
|
ret.duration = duration;
|
2021-04-18 21:48:35 +08:00
|
|
|
ret.subBenchmarks = scanned.map((o, i): BenchmarkResult => {
|
2020-12-31 04:13:19 +08:00
|
|
|
const stepBm = this.outputToBMResult(this.steps[i].name, o);
|
|
|
|
stepBm.duration = stepTimer[i] / RUNS;
|
|
|
|
return stepBm;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2020-12-31 19:35:28 +08:00
|
|
|
outputToBMResult(name: string, output: any[] | ContentWrapper | Message<ContentWrapper>): BenchmarkResult {
|
2020-12-31 04:13:19 +08:00
|
|
|
if (output) {
|
|
|
|
if (Array.isArray(output)) {
|
|
|
|
return {
|
2021-04-18 21:48:35 +08:00
|
|
|
name,
|
2020-12-31 04:13:19 +08:00
|
|
|
duration: null,
|
|
|
|
items: output.length,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-12-31 19:35:28 +08:00
|
|
|
if (output instanceof ContentWrapper) {
|
2020-12-31 04:13:19 +08:00
|
|
|
return {
|
2021-04-18 21:48:35 +08:00
|
|
|
name,
|
2020-12-31 04:13:19 +08:00
|
|
|
duration: null,
|
|
|
|
contentWrapper: output
|
|
|
|
};
|
|
|
|
}
|
2020-12-31 19:35:28 +08:00
|
|
|
if (output instanceof Message) {
|
|
|
|
const msg = output.result;
|
|
|
|
if (Array.isArray(msg)) {
|
|
|
|
return {
|
2021-04-18 21:48:35 +08:00
|
|
|
name,
|
2020-12-31 19:35:28 +08:00
|
|
|
duration: null,
|
|
|
|
items: msg.length,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (msg instanceof ContentWrapper) {
|
|
|
|
return {
|
2021-04-18 21:48:35 +08:00
|
|
|
name,
|
2020-12-31 19:35:28 +08:00
|
|
|
duration: null,
|
|
|
|
contentWrapper: msg
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
2020-12-31 04:13:19 +08:00
|
|
|
|
|
|
|
}
|
|
|
|
return {
|
2021-04-18 21:48:35 +08:00
|
|
|
name,
|
2020-12-31 04:13:19 +08:00
|
|
|
duration: null
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
async scanSteps(): Promise<any[]> {
|
2020-12-31 19:35:28 +08:00
|
|
|
const request = Utils.clone(this.request);
|
2020-12-31 04:13:19 +08:00
|
|
|
const stepOutput = new Array(this.steps.length);
|
|
|
|
|
|
|
|
for (let j = 0; j < this.steps.length; ++j) {
|
|
|
|
if (this.beforeEach) {
|
|
|
|
await this.beforeEach();
|
|
|
|
}
|
|
|
|
for (let i = 0; i <= j; ++i) {
|
2020-12-31 19:35:28 +08:00
|
|
|
stepOutput[j] = await this.steps[i].fn(request);
|
2020-12-31 04:13:19 +08:00
|
|
|
}
|
|
|
|
if (this.afterEach) {
|
|
|
|
await this.afterEach();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return stepOutput;
|
|
|
|
}
|
|
|
|
|
|
|
|
async runOneRound(stepTimer: number[]): Promise<number[]> {
|
2020-12-31 19:35:28 +08:00
|
|
|
const request = Utils.clone(this.request);
|
2020-12-31 04:13:19 +08:00
|
|
|
for (let i = 0; i < this.steps.length; ++i) {
|
|
|
|
const start = process.hrtime();
|
2020-12-31 19:35:28 +08:00
|
|
|
await this.steps[i].fn(request);
|
2020-12-31 04:13:19 +08:00
|
|
|
const end = process.hrtime(start);
|
|
|
|
stepTimer[i] += (end[0] * 1000 + end[1] / 1000000);
|
|
|
|
}
|
|
|
|
return stepTimer;
|
|
|
|
}
|
|
|
|
|
2021-04-18 21:48:35 +08:00
|
|
|
addAStep(step: BenchmarkStep): void {
|
2020-12-31 04:13:19 +08:00
|
|
|
this.steps.push(step);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|