1
0
mirror of https://github.com/bpatrik/pigallery2.git synced 2024-12-23 01:27:14 +02:00
pigallery2/benchmark/Benchmark.ts
Patrik J. Braun 315c174ff9 Adding experiment support and disable ability to benchmark
(cherry picked from commit dba4c8bf75ed76f41ba24d945bf7d22efb9665c2)
2021-05-15 09:27:42 +02:00

210 lines
5.7 KiB
TypeScript

import {BenchmarkResult} from './BenchmarkRunner';
import {ContentWrapper} from '../src/common/entities/ConentWrapper';
import {Express, NextFunction} from 'express';
import {Utils} from '../src/common/Utils';
import {Message} from '../src/common/entities/Message';
import {ActiveExperiments, Experiments} from './Experiments';
export interface BenchmarkStep {
name: string;
fn: ((input?: any) => Promise<ContentWrapper | any[] | void>);
}
/**
* This class converts PiGallery2 Routers to benchamrkable steps to the Benchmark class
*/
class BMExpressApp {
readonly benchmark: Benchmark;
constructor(benchmark: Benchmark) {
this.benchmark = benchmark;
}
get(match: string | string[], ...functions: ((req: any, res: any, next: NextFunction) => void)[]): void {
functions.forEach((f): void => {
this.benchmark.addAStep({
name: this.camelToSpaceSeparated(f.name),
fn: (request: any): Promise<void> => this.nextToPromise(f, request)
});
});
}
private camelToSpaceSeparated(text: string): string {
const result = (text.replace(/([A-Z])/g, ' $1')).toLocaleLowerCase();
return result.charAt(0).toUpperCase() + result.slice(1);
}
private nextToPromise(fn: (req: any, res: any, next: NextFunction) => void, request: any): Promise<void> {
return new Promise<void>((resolve, reject): void => {
const response = {
header: (): void => {
},
json: (data: any): void => {
resolve(data);
}
};
fn(request, response, (err?: any): void => {
if (err) {
return reject(err);
}
resolve(request.resultPipe);
});
});
}
}
export class Benchmark {
steps: BenchmarkStep[] = [];
name: string;
request: any;
beforeEach: () => Promise<any>;
afterEach: () => Promise<any>;
private readonly bmExpressApp: BMExpressApp;
constructor(name: string,
request: any = {},
beforeEach?: () => Promise<any>,
afterEach?: () => Promise<any>) {
this.name = name;
this.request = request;
this.beforeEach = beforeEach;
this.afterEach = afterEach;
this.bmExpressApp = new BMExpressApp(this);
}
get BmExpressApp(): Express {
return (this.bmExpressApp as unknown) as Express;
}
async run(RUNS: number): Promise<BenchmarkResult[]> {
const ret = [await this.runAnExperiment(RUNS)];
for (const exp of Object.values(Experiments)) {
for (const group of Object.values(exp.groups)) {
ActiveExperiments[exp.name] = group;
ret.push(await this.runAnExperiment(RUNS));
ret[ret.length - 1].experiment = exp.name + '=' + group;
}
delete ActiveExperiments[exp.name];
}
return ret;
}
async runAnExperiment(RUNS: number): Promise<BenchmarkResult> {
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;
ret.subBenchmarks = scanned.map((o, i): BenchmarkResult => {
const stepBm = this.outputToBMResult(this.steps[i].name, o);
stepBm.duration = stepTimer[i] / RUNS;
return stepBm;
}
);
return ret;
}
outputToBMResult(name: string, output: any[] | ContentWrapper | Message<ContentWrapper>): BenchmarkResult {
if (output) {
if (Array.isArray(output)) {
return {
name,
duration: null,
items: output.length,
};
}
if (output instanceof ContentWrapper) {
return {
name,
duration: null,
contentWrapper: output
};
}
if (output instanceof Message) {
const msg = output.result;
if (Array.isArray(msg)) {
return {
name,
duration: null,
items: msg.length,
};
}
if (msg instanceof ContentWrapper) {
return {
name,
duration: null,
contentWrapper: msg
};
}
}
}
return {
name,
duration: null
};
}
async scanSteps(): Promise<any[]> {
const request = Utils.clone(this.request);
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) {
stepOutput[j] = await this.steps[i].fn(request);
}
if (this.afterEach) {
await this.afterEach();
}
}
return stepOutput;
}
async runOneRound(stepTimer: number[]): Promise<number[]> {
const request = Utils.clone(this.request);
for (let i = 0; i < this.steps.length; ++i) {
const start = process.hrtime();
await this.steps[i].fn(request);
const end = process.hrtime(start);
stepTimer[i] += (end[0] * 1000 + end[1] / 1000000);
}
return stepTimer;
}
addAStep(step: BenchmarkStep): void {
this.steps.push(step);
}
}