mirror of
https://github.com/xuthus83/pigallery2.git
synced 2025-01-14 14:43:17 +08:00
Adding benchmark to release code
This commit is contained in:
parent
03789ecaf8
commit
0a8af49752
@ -2,7 +2,6 @@
|
|||||||
.idea/
|
.idea/
|
||||||
.git/
|
.git/
|
||||||
node_modules/
|
node_modules/
|
||||||
benchmark/
|
|
||||||
release/
|
release/
|
||||||
demo/
|
demo/
|
||||||
dist/
|
dist/
|
||||||
|
@ -11,6 +11,7 @@ import {ConfigProperty} from 'typeconfig/common';
|
|||||||
enumsAsString: true,
|
enumsAsString: true,
|
||||||
softReadonly: true,
|
softReadonly: true,
|
||||||
cli: {
|
cli: {
|
||||||
|
prefix: 'bm-config',
|
||||||
enable: {
|
enable: {
|
||||||
configPath: true,
|
configPath: true,
|
||||||
attachState: true,
|
attachState: true,
|
||||||
@ -31,6 +32,8 @@ export class PrivateConfigClass {
|
|||||||
path: string = 'demo/images';
|
path: string = 'demo/images';
|
||||||
@ConfigProperty({description: 'Describe your system setup'})
|
@ConfigProperty({description: 'Describe your system setup'})
|
||||||
system: string = '';
|
system: string = '';
|
||||||
|
@ConfigProperty({description: 'Number of times to run the benchmark'})
|
||||||
|
RUNS: number = 50;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
125
benchmark/Benchmark.ts
Normal file
125
benchmark/Benchmark.ts
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import {BenchmarkResult} from './BenchmarkRunner';
|
||||||
|
import {ContentWrapper} from '../src/common/entities/ConentWrapper';
|
||||||
|
|
||||||
|
export interface BenchmarkStep {
|
||||||
|
name: string;
|
||||||
|
fn: ((input?: any) => Promise<ContentWrapper | any[] | void>);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Benchmark {
|
||||||
|
|
||||||
|
|
||||||
|
steps: BenchmarkStep[] = [];
|
||||||
|
name: string;
|
||||||
|
inputCB: () => any;
|
||||||
|
beforeEach: () => Promise<any>;
|
||||||
|
afterEach: () => Promise<any>;
|
||||||
|
|
||||||
|
|
||||||
|
constructor(name: string,
|
||||||
|
inputCB?: () => any,
|
||||||
|
beforeEach?: () => Promise<any>,
|
||||||
|
afterEach?: () => Promise<any>) {
|
||||||
|
this.name = name;
|
||||||
|
this.inputCB = inputCB;
|
||||||
|
this.beforeEach = beforeEach;
|
||||||
|
this.afterEach = afterEach;
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(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) => {
|
||||||
|
const stepBm = this.outputToBMResult(this.steps[i].name, o);
|
||||||
|
stepBm.duration = stepTimer[i] / RUNS;
|
||||||
|
return stepBm;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
outputToBMResult(name: string, output: any[] | ContentWrapper): BenchmarkResult {
|
||||||
|
if (output) {
|
||||||
|
if (Array.isArray(output)) {
|
||||||
|
return {
|
||||||
|
name: name,
|
||||||
|
duration: null,
|
||||||
|
items: output.length,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output.directory || output.searchResult) {
|
||||||
|
return {
|
||||||
|
name: name,
|
||||||
|
duration: null,
|
||||||
|
contentWrapper: output
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
name: name,
|
||||||
|
duration: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async scanSteps(): Promise<any[]> {
|
||||||
|
let pipe = this.inputCB ? this.inputCB() : null;
|
||||||
|
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) {
|
||||||
|
pipe = await this.steps[i].fn(pipe);
|
||||||
|
}
|
||||||
|
stepOutput[j] = pipe;
|
||||||
|
if (this.afterEach) {
|
||||||
|
await this.afterEach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stepOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
async runOneRound(stepTimer: number[]): Promise<number[]> {
|
||||||
|
let pipe = this.inputCB ? this.inputCB() : null;
|
||||||
|
for (let i = 0; i < this.steps.length; ++i) {
|
||||||
|
const start = process.hrtime();
|
||||||
|
pipe = await this.steps[i].fn(pipe);
|
||||||
|
const end = process.hrtime(start);
|
||||||
|
stepTimer[i] += (end[0] * 1000 + end[1] / 1000000);
|
||||||
|
}
|
||||||
|
return stepTimer;
|
||||||
|
}
|
||||||
|
|
||||||
|
addAStep(step: BenchmarkStep) {
|
||||||
|
this.steps.push(step);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
215
benchmark/BenchmarkRunner.ts
Normal file
215
benchmark/BenchmarkRunner.ts
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
import {Config} from '../src/common/config/private/Config';
|
||||||
|
import {ObjectManagers} from '../src/backend/model/ObjectManagers';
|
||||||
|
import {DiskMangerWorker} from '../src/backend/model/threading/DiskMangerWorker';
|
||||||
|
import {IndexingManager} from '../src/backend/model/database/sql/IndexingManager';
|
||||||
|
import {SearchManager} from '../src/backend/model/database/sql/SearchManager';
|
||||||
|
import * as util from 'util';
|
||||||
|
import * as rimraf from 'rimraf';
|
||||||
|
import {SearchTypes} from '../src/common/entities/AutoCompleteItem';
|
||||||
|
import {Utils} from '../src/common/Utils';
|
||||||
|
import {DirectoryDTO} from '../src/common/entities/DirectoryDTO';
|
||||||
|
import {ServerConfig} from '../src/common/config/private/PrivateConfig';
|
||||||
|
import {ProjectPath} from '../src/backend/ProjectPath';
|
||||||
|
import {PersonMWs} from '../src/backend/middlewares/PersonMWs';
|
||||||
|
import {ThumbnailGeneratorMWs} from '../src/backend/middlewares/thumbnail/ThumbnailGeneratorMWs';
|
||||||
|
import {Benchmark} from './Benchmark';
|
||||||
|
import {IndexingJob} from '../src/backend/model/jobs/jobs/IndexingJob';
|
||||||
|
import {IJob} from '../src/backend/model/jobs/jobs/IJob';
|
||||||
|
import {JobProgressStates} from '../src/common/entities/job/JobProgressDTO';
|
||||||
|
import {JobProgress} from '../src/backend/model/jobs/jobs/JobProgress';
|
||||||
|
import {GalleryMWs} from '../src/backend/middlewares/GalleryMWs';
|
||||||
|
import {UserDTO, UserRoles} from '../src/common/entities/UserDTO';
|
||||||
|
import {ContentWrapper} from '../src/common/entities/ConentWrapper';
|
||||||
|
|
||||||
|
const rimrafPR = util.promisify(rimraf);
|
||||||
|
|
||||||
|
export interface BenchmarkResult {
|
||||||
|
name: string;
|
||||||
|
duration: number;
|
||||||
|
contentWrapper?: ContentWrapper;
|
||||||
|
items?: number;
|
||||||
|
subBenchmarks?: BenchmarkResult[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BMIndexingManager extends IndexingManager {
|
||||||
|
|
||||||
|
public async saveToDB(scannedDirectory: DirectoryDTO): Promise<void> {
|
||||||
|
return super.saveToDB(scannedDirectory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BenchmarkRunner {
|
||||||
|
|
||||||
|
|
||||||
|
constructor(public RUNS: number) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async bmSaveDirectory(): Promise<BenchmarkResult> {
|
||||||
|
await this.resetDB();
|
||||||
|
const dir = await DiskMangerWorker.scanDirectory('./');
|
||||||
|
const bm = new Benchmark('Saving directory to DB', null, () => this.resetDB());
|
||||||
|
bm.addAStep({
|
||||||
|
name: 'Saving directory to DB',
|
||||||
|
fn: () => {
|
||||||
|
const im = new BMIndexingManager();
|
||||||
|
return im.saveToDB(dir);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return await bm.run(this.RUNS);
|
||||||
|
}
|
||||||
|
|
||||||
|
async bmScanDirectory(): Promise<BenchmarkResult> {
|
||||||
|
const bm = new Benchmark('Scanning directory');
|
||||||
|
bm.addAStep({
|
||||||
|
name: 'Scanning directory',
|
||||||
|
fn: async () => new ContentWrapper(await DiskMangerWorker.scanDirectory('./'))
|
||||||
|
});
|
||||||
|
return await bm.run(this.RUNS);
|
||||||
|
}
|
||||||
|
|
||||||
|
async bmListDirectory(): Promise<BenchmarkResult> {
|
||||||
|
await this.setupDB();
|
||||||
|
Config.Server.Indexing.reIndexingSensitivity = ServerConfig.ReIndexingSensitivity.low;
|
||||||
|
const bm = new Benchmark('List directory',
|
||||||
|
null,
|
||||||
|
async () => {
|
||||||
|
await ObjectManagers.reset();
|
||||||
|
await ObjectManagers.InitSQLManagers();
|
||||||
|
});
|
||||||
|
bm.addAStep({
|
||||||
|
name: 'List directory',
|
||||||
|
fn: (input) => this.nextToPromise(GalleryMWs.listDirectory, input, {directory: '/'})
|
||||||
|
});
|
||||||
|
bm.addAStep({
|
||||||
|
name: 'Add Thumbnail information',
|
||||||
|
fn: (input) => this.nextToPromise(ThumbnailGeneratorMWs.addThumbnailInformation, input)
|
||||||
|
});
|
||||||
|
bm.addAStep({
|
||||||
|
name: 'Clean Up Gallery Result',
|
||||||
|
fn: (input) => this.nextToPromise(GalleryMWs.cleanUpGalleryResults, input)
|
||||||
|
});
|
||||||
|
return await bm.run(this.RUNS);
|
||||||
|
}
|
||||||
|
|
||||||
|
async bmListPersons(): Promise<BenchmarkResult> {
|
||||||
|
await this.setupDB();
|
||||||
|
Config.Server.Indexing.reIndexingSensitivity = ServerConfig.ReIndexingSensitivity.low;
|
||||||
|
const bm = new Benchmark('Listing Faces', null, async () => {
|
||||||
|
await ObjectManagers.reset();
|
||||||
|
await ObjectManagers.InitSQLManagers();
|
||||||
|
});
|
||||||
|
bm.addAStep({
|
||||||
|
name: 'List Persons',
|
||||||
|
fn: (input) => this.nextToPromise(PersonMWs.listPersons, input)
|
||||||
|
});
|
||||||
|
bm.addAStep({
|
||||||
|
name: 'Add sample photo',
|
||||||
|
fn: (input) => this.nextToPromise(PersonMWs.addSamplePhotoForAll, input)
|
||||||
|
});
|
||||||
|
bm.addAStep({
|
||||||
|
name: 'Add thumbnail info',
|
||||||
|
fn: (input) => this.nextToPromise(ThumbnailGeneratorMWs.addThumbnailInfoForPersons, input)
|
||||||
|
});
|
||||||
|
bm.addAStep({
|
||||||
|
name: 'Remove sample photo',
|
||||||
|
fn: (input) => this.nextToPromise(PersonMWs.removeSamplePhotoForAll, input)
|
||||||
|
});
|
||||||
|
return await bm.run(this.RUNS);
|
||||||
|
}
|
||||||
|
|
||||||
|
async bmAllSearch(text: string): Promise<{ result: BenchmarkResult, searchType: SearchTypes }[]> {
|
||||||
|
await this.setupDB();
|
||||||
|
const types = Utils.enumToArray(SearchTypes).map(a => a.key).concat([null]);
|
||||||
|
const results: { result: BenchmarkResult, searchType: SearchTypes }[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < types.length; i++) {
|
||||||
|
const bm = new Benchmark('Searching');
|
||||||
|
bm.addAStep({
|
||||||
|
name: 'Searching',
|
||||||
|
fn: async () => {
|
||||||
|
const sm = new SearchManager();
|
||||||
|
return new ContentWrapper(null, await sm.search(text, types[i]));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
results.push({result: await bm.run(this.RUNS), searchType: types[i]});
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
async bmInstantSearch(text: string): Promise<BenchmarkResult> {
|
||||||
|
await this.setupDB();
|
||||||
|
const bm = new Benchmark('Instant search');
|
||||||
|
bm.addAStep({
|
||||||
|
name: 'Instant search',
|
||||||
|
fn: async () => {
|
||||||
|
const sm = new SearchManager();
|
||||||
|
return new ContentWrapper(null, await sm.instantSearch(text));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return await bm.run(this.RUNS);
|
||||||
|
}
|
||||||
|
|
||||||
|
async bmAutocomplete(text: string): Promise<BenchmarkResult> {
|
||||||
|
await this.setupDB();
|
||||||
|
const bm = new Benchmark('Auto complete');
|
||||||
|
bm.addAStep({
|
||||||
|
name: 'Auto complete',
|
||||||
|
fn: () => {
|
||||||
|
const sm = new SearchManager();
|
||||||
|
return sm.autocomplete(text);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return await bm.run(this.RUNS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private nextToPromise(fn: (req: any, res: any, next: Function) => void, input?: any, params = {}) {
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
const request = {
|
||||||
|
resultPipe: input,
|
||||||
|
params: params,
|
||||||
|
query: {},
|
||||||
|
session: {user: <UserDTO>{name: UserRoles[UserRoles.Admin], role: UserRoles.Admin}}
|
||||||
|
};
|
||||||
|
fn(request, resolve, (err?: any) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
resolve(request.resultPipe);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private resetDB = async () => {
|
||||||
|
Config.Server.Threading.enabled = false;
|
||||||
|
await ObjectManagers.reset();
|
||||||
|
await rimrafPR(ProjectPath.DBFolder);
|
||||||
|
Config.Server.Database.type = ServerConfig.DatabaseType.sqlite;
|
||||||
|
Config.Server.Jobs.scheduled = [];
|
||||||
|
await ObjectManagers.InitSQLManagers();
|
||||||
|
};
|
||||||
|
|
||||||
|
private setupDB(): Promise<void> {
|
||||||
|
Config.Server.Threading.enabled = false;
|
||||||
|
return new Promise<void>(async (resolve, reject) => {
|
||||||
|
try {
|
||||||
|
await this.resetDB();
|
||||||
|
const indexingJob = new IndexingJob();
|
||||||
|
|
||||||
|
indexingJob.JobListener = {
|
||||||
|
onJobFinished: (job: IJob<any>, state: JobProgressStates, soloRun: boolean) => {
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
|
||||||
|
onProgressUpdate: (progress: JobProgress) => {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
indexingJob.start().catch(console.error);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,164 +0,0 @@
|
|||||||
import {SQLConnection} from '../src/backend/model/database/sql/SQLConnection';
|
|
||||||
import {Config} from '../src/common/config/private/Config';
|
|
||||||
import {ObjectManagers} from '../src/backend/model/ObjectManagers';
|
|
||||||
import {DiskMangerWorker} from '../src/backend/model/threading/DiskMangerWorker';
|
|
||||||
import {IndexingManager} from '../src/backend/model/database/sql/IndexingManager';
|
|
||||||
import {SearchManager} from '../src/backend/model/database/sql/SearchManager';
|
|
||||||
import * as util from 'util';
|
|
||||||
import * as rimraf from 'rimraf';
|
|
||||||
import {SearchTypes} from '../src/common/entities/AutoCompleteItem';
|
|
||||||
import {Utils} from '../src/common/Utils';
|
|
||||||
import {GalleryManager} from '../src/backend/model/database/sql/GalleryManager';
|
|
||||||
import {DirectoryDTO} from '../src/common/entities/DirectoryDTO';
|
|
||||||
import {ServerConfig} from '../src/common/config/private/PrivateConfig';
|
|
||||||
import {ProjectPath} from '../src/backend/ProjectPath';
|
|
||||||
import {PersonMWs} from '../src/backend/middlewares/PersonMWs';
|
|
||||||
import {ThumbnailGeneratorMWs} from '../src/backend/middlewares/thumbnail/ThumbnailGeneratorMWs';
|
|
||||||
|
|
||||||
const rimrafPR = util.promisify(rimraf);
|
|
||||||
|
|
||||||
export interface BenchmarkResult {
|
|
||||||
duration: number;
|
|
||||||
directories?: number;
|
|
||||||
media?: number;
|
|
||||||
items?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class BMIndexingManager extends IndexingManager {
|
|
||||||
|
|
||||||
public async saveToDB(scannedDirectory: DirectoryDTO): Promise<void> {
|
|
||||||
return super.saveToDB(scannedDirectory);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Benchmarks {
|
|
||||||
|
|
||||||
constructor(public RUNS: number) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
async bmSaveDirectory(): Promise<BenchmarkResult> {
|
|
||||||
await this.resetDB();
|
|
||||||
const dir = await DiskMangerWorker.scanDirectory('./');
|
|
||||||
const im = new BMIndexingManager();
|
|
||||||
return await this.benchmark(() => im.saveToDB(dir), () => this.resetDB());
|
|
||||||
}
|
|
||||||
|
|
||||||
async bmScanDirectory(): Promise<BenchmarkResult> {
|
|
||||||
return await this.benchmark(() => DiskMangerWorker.scanDirectory('./'));
|
|
||||||
}
|
|
||||||
|
|
||||||
async bmListDirectory(): Promise<BenchmarkResult> {
|
|
||||||
const gm = new GalleryManager();
|
|
||||||
await this.setupDB();
|
|
||||||
Config.Server.Indexing.reIndexingSensitivity = ServerConfig.ReIndexingSensitivity.low;
|
|
||||||
return await this.benchmark(() => gm.listDirectory('./'));
|
|
||||||
}
|
|
||||||
|
|
||||||
async bmListPersons(): Promise<BenchmarkResult> {
|
|
||||||
await this.setupDB();
|
|
||||||
Config.Server.Indexing.reIndexingSensitivity = ServerConfig.ReIndexingSensitivity.low;
|
|
||||||
return await this.benchmark(async () => {
|
|
||||||
await ObjectManagers.reset();
|
|
||||||
const req = {resultPipe: <any>null};
|
|
||||||
await this.nextToPromise(PersonMWs.listPersons, req, null);
|
|
||||||
await this.nextToPromise(PersonMWs.addSamplePhotoForAll, req, null);
|
|
||||||
await this.nextToPromise(ThumbnailGeneratorMWs.addThumbnailInfoForPersons, req, null);
|
|
||||||
await this.nextToPromise(PersonMWs.removeSamplePhotoForAll, req, null);
|
|
||||||
return req.resultPipe;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async bmAllSearch(text: string): Promise<{ result: BenchmarkResult, searchType: SearchTypes }[]> {
|
|
||||||
await this.setupDB();
|
|
||||||
const types = Utils.enumToArray(SearchTypes).map(a => a.key).concat([null]);
|
|
||||||
const results: { result: BenchmarkResult, searchType: SearchTypes }[] = [];
|
|
||||||
const sm = new SearchManager();
|
|
||||||
for (let i = 0; i < types.length; i++) {
|
|
||||||
results.push({result: await this.benchmark(() => sm.search(text, types[i])), searchType: types[i]});
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
async bmInstantSearch(text: string): Promise<BenchmarkResult> {
|
|
||||||
await this.setupDB();
|
|
||||||
const sm = new SearchManager();
|
|
||||||
return await this.benchmark(() => sm.instantSearch(text));
|
|
||||||
}
|
|
||||||
|
|
||||||
async bmAutocomplete(text: string): Promise<BenchmarkResult> {
|
|
||||||
await this.setupDB();
|
|
||||||
const sm = new SearchManager();
|
|
||||||
return await this.benchmark(() => sm.autocomplete(text));
|
|
||||||
}
|
|
||||||
|
|
||||||
private nextToPromise(fn: (req: any, res: any, next: Function) => void, request: any, response: any) {
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
|
||||||
fn(request, resolve, (err?: any) => {
|
|
||||||
if (err) {
|
|
||||||
return reject(err);
|
|
||||||
}
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async benchmark(fn: () => Promise<{ media: any[], directories: any[] } | any[] | void>,
|
|
||||||
beforeEach: () => Promise<any> = null,
|
|
||||||
afterEach: () => Promise<any> = null) {
|
|
||||||
const scanned = await fn();
|
|
||||||
const start = process.hrtime();
|
|
||||||
let skip = 0;
|
|
||||||
for (let i = 0; i < this.RUNS; i++) {
|
|
||||||
if (beforeEach) {
|
|
||||||
const startSkip = process.hrtime();
|
|
||||||
await beforeEach();
|
|
||||||
const endSkip = process.hrtime(startSkip);
|
|
||||||
skip += (endSkip[0] * 1000 + endSkip[1] / 1000000);
|
|
||||||
}
|
|
||||||
await fn();
|
|
||||||
if (afterEach) {
|
|
||||||
const startSkip = process.hrtime();
|
|
||||||
await 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) / this.RUNS;
|
|
||||||
|
|
||||||
if (!scanned) {
|
|
||||||
return {
|
|
||||||
duration: duration
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(scanned)) {
|
|
||||||
return {
|
|
||||||
duration: duration,
|
|
||||||
items: scanned.length
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
duration: duration,
|
|
||||||
media: scanned.media.length,
|
|
||||||
directories: scanned.directories.length
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private resetDB = async () => {
|
|
||||||
await SQLConnection.close();
|
|
||||||
await rimrafPR(ProjectPath.DBFolder);
|
|
||||||
Config.Server.Database.type = ServerConfig.DatabaseType.sqlite;
|
|
||||||
await ObjectManagers.InitSQLManagers();
|
|
||||||
};
|
|
||||||
|
|
||||||
private async setupDB() {
|
|
||||||
const im = new BMIndexingManager();
|
|
||||||
await this.resetDB();
|
|
||||||
const dir = await DiskMangerWorker.scanDirectory('./');
|
|
||||||
await im.saveToDB(dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -2,6 +2,93 @@
|
|||||||
|
|
||||||
These results are created mostly for development, but the results are public for curious users.
|
These results are created mostly for development, but the results are public for curious users.
|
||||||
|
|
||||||
|
## PiGallery2 v1.8.2, 30.12.2020
|
||||||
|
**System**: Intel(R) Core(TM) i7-6700HQ CPU @ 2.60GHz, 16GB Ram, SHDD: 1TB, 5400 rpm
|
||||||
|
|
||||||
|
**Gallery**: directories: 0 media: 341, faces: 65
|
||||||
|
|
||||||
|
| Action | Sub action | Action details | Average Duration | Details |
|
||||||
|
|:------:|:----------:|:--------------:|:----------------:|:-------:|
|
||||||
|
| **Scanning directory** | | | 1526.7 ms | media: 341, directories:0 |
|
||||||
|
| **Saving directory to DB** | | | 679.9 ms | - |
|
||||||
|
| **List directory** | | | 61.1 ms | media: 341, directories:0 |
|
||||||
|
| | List directory | | 39.7 ms | media: 341, directories:0 |
|
||||||
|
| | Add Thumbnail information | | 19.3 ms | media: 341, directories:0 |
|
||||||
|
| | Clean Up Gallery Result | | 2.0 ms | media: 341, directories:0 |
|
||||||
|
| **Listing Faces** | | | 8.4 ms | items: 1 |
|
||||||
|
| | List Persons | | 1.0 ms | items: 1 |
|
||||||
|
| | Add sample photo | | 7.1 ms | items: 1 |
|
||||||
|
| | Add thumbnail info | | 0.1 ms | items: 1 |
|
||||||
|
| | Remove sample photo | | 0.0 ms | items: 1 |
|
||||||
|
| **Searching** | | `a` as `directory` | 3.3 ms | media: 0, directories:0 |
|
||||||
|
| **Searching** | | `a` as `person` | 11.6 ms | media: 65, directories:0 |
|
||||||
|
| **Searching** | | `a` as `keyword` | 38.0 ms | media: 339, directories:0 |
|
||||||
|
| **Searching** | | `a` as `position` | 31.7 ms | media: 282, directories:0 |
|
||||||
|
| **Searching** | | `a` as `photo` | 3.2 ms | media: 0, directories:0 |
|
||||||
|
| **Searching** | | `a` as `video` | 3.2 ms | media: 0, directories:0 |
|
||||||
|
| **Searching** | | `a` as `any` | 39.3 ms | media: 339, directories:0 |
|
||||||
|
| **Instant search** | | `a` | 6.7 ms | media: 10, directories:0 |
|
||||||
|
| **Auto complete** | | `a` | 6.7 ms | items: 10 |
|
||||||
|
*Measurements run 2 times and an average was calculated.
|
||||||
|
|
||||||
|
|
||||||
|
## PiGallery2 v1.8.2, 30.12.2020
|
||||||
|
**System**: Intel(R) Core(TM) i7-6700HQ CPU @ 2.60GHz, 16GB Ram, SHDD: 1TB, 5400 rpm
|
||||||
|
|
||||||
|
**Gallery**: directories: 0 media: 341, faces: 65
|
||||||
|
|
||||||
|
| Action | Sub action | Action details | Average Duration | Details |
|
||||||
|
|:------:|:----------:|:--------------:|:----------------:|:-------:|
|
||||||
|
| **Scanning directory** | | | 1459.3 ms | media: 341, directories:0 |
|
||||||
|
| **Saving directory to DB** | | | 654.9 ms | - |
|
||||||
|
| **List directory** | | | 70.4 ms | - |
|
||||||
|
| | List directory | | 42.1 ms | - |
|
||||||
|
| | Add Thumbnail information | | 25.8 ms | - |
|
||||||
|
| | Clean Up Gallery Result | | 2.3 ms | - |
|
||||||
|
| **Listing Faces** | | | 10.5 ms | items: 1 |
|
||||||
|
| | List Persons | | 1.0 ms | items: 1 |
|
||||||
|
| | Add sample photo | | 9.1 ms | items: 1 |
|
||||||
|
| | Add thumbnail info | | 0.2 ms | items: 1 |
|
||||||
|
| | Remove sample photo | | 0.0 ms | items: 1 |
|
||||||
|
| **Searching** | | `a` as `directory` | 3.3 ms | - |
|
||||||
|
| **Searching** | | `a` as `person` | 11.7 ms | media: 65, directories:0 |
|
||||||
|
| **Searching** | | `a` as `keyword` | 40.4 ms | media: 339, directories:0 |
|
||||||
|
| **Searching** | | `a` as `position` | 30.4 ms | media: 282, directories:0 |
|
||||||
|
| **Searching** | | `a` as `photo` | 2.7 ms | - |
|
||||||
|
| **Searching** | | `a` as `video` | 3.5 ms | - |
|
||||||
|
| **Searching** | | `a` as `any` | 36.9 ms | media: 339, directories:0 |
|
||||||
|
| **Instant search** | | `a` | 5.4 ms | media: 10, directories:0 |
|
||||||
|
| **Auto complete** | | `a` | 6.7 ms | items: 10 |
|
||||||
|
*Measurements run 2 times and an average was calculated.
|
||||||
|
|
||||||
|
|
||||||
|
## PiGallery2 v1.8.2, 30.12.2020
|
||||||
|
**System**: Intel(R) Core(TM) i7-6700HQ CPU @ 2.60GHz, 16GB Ram, SHDD: 1TB, 5400 rpm
|
||||||
|
|
||||||
|
**Gallery**: directories: 0 media: 341, faces: 65
|
||||||
|
|
||||||
|
| Action | Sub action | Action details | Average Duration | Details |
|
||||||
|
|:------:|:----------:|:--------------:|:----------------:|:-------:|
|
||||||
|
| Scanning directory | | | 1746.5 ms | media: 341, directories:0 |
|
||||||
|
| Saving directory to DB | | | 994.1 ms | - |
|
||||||
|
| Scanning directory | | | 65.3 ms | media: 341, directories:0 |
|
||||||
|
| Listing Faces | | | 19.0 ms | items: 1 |
|
||||||
|
| | List Persons | | 1.9 ms | items: 1 |
|
||||||
|
| | Add sample photo | | 16.6 ms | items: 1 |
|
||||||
|
| | Add thumbnail info | | 0.3 ms | items: 1 |
|
||||||
|
| | Remove sample photo | | 0.0 ms | items: 1 |
|
||||||
|
| Searching | | `a` as `directory` | 4.1 ms | - |
|
||||||
|
| Searching | | `a` as `person` | 16.1 ms | media: 65, directories:0 |
|
||||||
|
| Searching | | `a` as `keyword` | 41.6 ms | media: 339, directories:0 |
|
||||||
|
| Searching | | `a` as `position` | 67.1 ms | media: 282, directories:0 |
|
||||||
|
| Searching | | `a` as `photo` | 5.4 ms | - |
|
||||||
|
| Searching | | `a` as `video` | 4.3 ms | - |
|
||||||
|
| Searching | | `a` as `any` | 53.5 ms | media: 339, directories:0 |
|
||||||
|
| Instant search | | `a` | 5.3 ms | media: 10, directories:0 |
|
||||||
|
| Auto complete | | `a` | 7.2 ms | items: 10 |
|
||||||
|
*Measurements run 2 times and an average was calculated.
|
||||||
|
|
||||||
|
|
||||||
## PiGallery2 v1.5.8, 26.01.2019
|
## PiGallery2 v1.5.8, 26.01.2019
|
||||||
|
|
||||||
**System**: Intel(R) Core(TM) i7-6700HQ CPU @ 2.60GHz, 16GB Ram, SHDD: 1TB, 5400 rpm
|
**System**: Intel(R) Core(TM) i7-6700HQ CPU @ 2.60GHz, 16GB Ram, SHDD: 1TB, 5400 rpm
|
||||||
|
19
benchmark/docker-compose/docker-compose.yml
Normal file
19
benchmark/docker-compose/docker-compose.yml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
version: '3'
|
||||||
|
services:
|
||||||
|
pigallery2:
|
||||||
|
entrypoint: [ "node", "./benchmark/index", "--config-path=/app/data/config/config.json", "--bm-config-path=/app/data/config/bm_config.json" ]
|
||||||
|
image: bpatrik/pigallery2:latest
|
||||||
|
container_name: pigallery2
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
volumes:
|
||||||
|
- "./pigallery2/benchmark_config:/app/data/config" # CHANGE ME
|
||||||
|
- "db-benchmark-data:/app/data/db"
|
||||||
|
- "./pigallery2/images:/app/data/images" # CHANGE ME
|
||||||
|
- "./pigallery2/tmp:/app/data/tmp" # CHANGE ME
|
||||||
|
expose:
|
||||||
|
- "80"
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
db-benchmark-data:
|
@ -1,18 +1,17 @@
|
|||||||
import {Config} from '../src/common/config/private/Config';
|
import {Config} from '../src/common/config/private/Config';
|
||||||
import * as path from 'path';
|
|
||||||
import {ProjectPath} from '../src/backend/ProjectPath';
|
import {ProjectPath} from '../src/backend/ProjectPath';
|
||||||
import {BenchmarkResult, Benchmarks} from './Benchmarks';
|
import {BenchmarkResult, BenchmarkRunner} from './BenchmarkRunner';
|
||||||
import {SearchTypes} from '../src/common/entities/AutoCompleteItem';
|
import {SearchTypes} from '../src/common/entities/AutoCompleteItem';
|
||||||
import {Utils} from '../src/common/Utils';
|
import {Utils} from '../src/common/Utils';
|
||||||
import {DiskMangerWorker} from '../src/backend/model/threading/DiskMangerWorker';
|
import {DiskMangerWorker} from '../src/backend/model/threading/DiskMangerWorker';
|
||||||
import {BMConfig} from './BMConfig';
|
import {BMConfig} from './BMConfig';
|
||||||
import {GalleryManager} from '../src/backend/model/database/sql/GalleryManager';
|
import {GalleryManager} from '../src/backend/model/database/sql/GalleryManager';
|
||||||
|
import {PersonManager} from '../src/backend/model/database/sql/PersonManager';
|
||||||
|
|
||||||
|
|
||||||
Config.Server.Media.folder = BMConfig.path;
|
Config.Server.Media.folder = BMConfig.path;
|
||||||
const dbFolder = path.join(__dirname, './../');
|
|
||||||
ProjectPath.reset();
|
ProjectPath.reset();
|
||||||
const RUNS = 50;
|
const RUNS = BMConfig.RUNS;
|
||||||
|
|
||||||
let resultsText = '';
|
let resultsText = '';
|
||||||
const printLine = (text: string) => {
|
const printLine = (text: string) => {
|
||||||
@ -28,54 +27,75 @@ const printHeader = async () => {
|
|||||||
printLine('**System**: ' + BMConfig.system);
|
printLine('**System**: ' + BMConfig.system);
|
||||||
const dir = await DiskMangerWorker.scanDirectory('./');
|
const dir = await DiskMangerWorker.scanDirectory('./');
|
||||||
const gm = new GalleryManager();
|
const gm = new GalleryManager();
|
||||||
printLine('**Gallery**: directories: ' +
|
const pm = new PersonManager();
|
||||||
|
printLine('\n**Gallery**: directories: ' +
|
||||||
dir.directories.length +
|
dir.directories.length +
|
||||||
' media: ' + dir.media.length +
|
' media: ' + dir.media.length +
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
', faces: ' + dir.media.reduce((p, c) => p + (c.metadata.faces || []).length, 0));
|
', all persons: ' + dir.media.reduce((p, c) => p + (c.metadata.faces || []).length, 0) +
|
||||||
|
',unique persons (faces): ' + (await pm.getAll()).length + '\n');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const printTableHeader = () => {
|
const printTableHeader = () => {
|
||||||
printLine('| action | action details | average time | details |');
|
printLine('| Action | Sub action | Action details | Average Duration | Details |');
|
||||||
printLine('|:------:|:--------------:|:------------:|:-------:|');
|
printLine('|:------:|:----------:|:--------------:|:----------------:|:-------:|');
|
||||||
};
|
};
|
||||||
const printResult = (result: BenchmarkResult, action: string, actionDetails: string = '') => {
|
const printResult = (result: BenchmarkResult, actionDetails: string = '', isSubResult = false) => {
|
||||||
console.log('benchmarked: ' + action);
|
console.log('benchmarked: ' + result.name);
|
||||||
let details = '-';
|
let details = '-';
|
||||||
if (result.items) {
|
if (result.items) {
|
||||||
details = 'items: ' + result.items;
|
details = 'items: ' + result.items;
|
||||||
}
|
}
|
||||||
if (result.media) {
|
if (result.contentWrapper) {
|
||||||
details = 'media: ' + result.media + ', directories:' + result.directories;
|
if (result.contentWrapper.directory) {
|
||||||
|
details = 'media: ' + result.contentWrapper.directory.media.length +
|
||||||
|
', directories:' + result.contentWrapper.directory.directories.length;
|
||||||
|
} else {
|
||||||
|
details = 'media: ' + result.contentWrapper.searchResult.media.length +
|
||||||
|
', directories:' + result.contentWrapper.searchResult.directories.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isSubResult) {
|
||||||
|
printLine('| | ' + result.name + ' | ' + actionDetails +
|
||||||
|
' | ' + (result.duration).toFixed(1) + ' ms | ' + details + ' |');
|
||||||
|
} else {
|
||||||
|
printLine('| **' + result.name + '** | | ' + actionDetails +
|
||||||
|
' | ' + (result.duration).toFixed(1) + ' ms | ' + details + ' |');
|
||||||
|
}
|
||||||
|
if (result.subBenchmarks && result.subBenchmarks.length > 1) {
|
||||||
|
for (let i = 0; i < result.subBenchmarks.length; i++) {
|
||||||
|
printResult(result.subBenchmarks[i], '', true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
printLine('| ' + action + ' | ' + actionDetails +
|
|
||||||
' | ' + (result.duration).toFixed(1) + 'ms | ' + details + ' |');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const run = async () => {
|
const run = async () => {
|
||||||
|
console.log('Running, RUNS:' + RUNS);
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
const bm = new Benchmarks(RUNS);
|
const bm = new BenchmarkRunner(RUNS);
|
||||||
|
|
||||||
// header
|
// header
|
||||||
await printHeader();
|
await printHeader();
|
||||||
printTableHeader();
|
printTableHeader();
|
||||||
printResult(await bm.bmScanDirectory(), 'Scanning directory');
|
|
||||||
printResult(await bm.bmSaveDirectory(), 'Saving directory');
|
printResult(await bm.bmScanDirectory());
|
||||||
printResult(await bm.bmListDirectory(), 'Listing Directory');
|
printResult(await bm.bmSaveDirectory());
|
||||||
printResult(await bm.bmListPersons(), 'Listing Faces');
|
printResult(await bm.bmListDirectory());
|
||||||
|
printResult(await bm.bmListPersons());
|
||||||
(await bm.bmAllSearch('a')).forEach(res => {
|
(await bm.bmAllSearch('a')).forEach(res => {
|
||||||
if (res.searchType !== null) {
|
if (res.searchType !== null) {
|
||||||
printResult(res.result, 'searching', '`a` as `' + SearchTypes[res.searchType] + '`');
|
printResult(res.result, '`a` as `' + SearchTypes[res.searchType] + '`');
|
||||||
} else {
|
} else {
|
||||||
printResult(res.result, 'searching', '`a` as `any`');
|
printResult(res.result, '`a` as `any`');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
printResult(await bm.bmInstantSearch('a'), 'instant search', '`a`');
|
printResult(await bm.bmInstantSearch('a'), '`a`');
|
||||||
printResult(await bm.bmAutocomplete('a'), 'auto complete', '`a`');
|
printResult(await bm.bmAutocomplete('a'), '`a`');
|
||||||
|
printLine('*Measurements run ' + RUNS + ' times and an average was calculated.');
|
||||||
console.log(resultsText);
|
console.log(resultsText);
|
||||||
console.log('run for : ' + ((Date.now() - start)).toFixed(1) + 'ms');
|
console.log('run for : ' + ((Date.now() - start)).toFixed(1) + 'ms');
|
||||||
};
|
};
|
||||||
|
|
||||||
run();
|
run().then(console.log).catch(console.error);
|
||||||
|
|
||||||
|
@ -38,7 +38,8 @@ const getSwitch = (name: string, def: string = null): string => {
|
|||||||
gulp.task('build-backend', function () {
|
gulp.task('build-backend', function () {
|
||||||
return gulp.src([
|
return gulp.src([
|
||||||
'src/common/**/*.ts',
|
'src/common/**/*.ts',
|
||||||
'src/backend/**/*.ts'], {base: '.'})
|
'src/backend/**/*.ts',
|
||||||
|
'benchmark/**/*.ts'], {base: '.'})
|
||||||
.pipe(tsBackendProject())
|
.pipe(tsBackendProject())
|
||||||
.js
|
.js
|
||||||
.pipe(gulp.dest('./release'));
|
.pipe(gulp.dest('./release'));
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import {DirectoryDTO} from '../../../../common/entities/DirectoryDTO';
|
import {DirectoryDTO} from '../../../../common/entities/DirectoryDTO';
|
||||||
|
|
||||||
export interface IIndexingManager {
|
export interface IIndexingManager {
|
||||||
|
SavingReady: Promise<void>
|
||||||
|
IsSavingInProgress: boolean;
|
||||||
|
|
||||||
indexDirectory(relativeDirectoryName: string): Promise<DirectoryDTO>;
|
indexDirectory(relativeDirectoryName: string): Promise<DirectoryDTO>;
|
||||||
|
|
||||||
resetDB(): Promise<void>;
|
resetDB(): Promise<void>;
|
||||||
|
@ -22,9 +22,15 @@ const LOG_TAG = '[IndexingManager]';
|
|||||||
|
|
||||||
export class IndexingManager implements IIndexingManager {
|
export class IndexingManager implements IIndexingManager {
|
||||||
|
|
||||||
|
SavingReady: Promise<void> = null;
|
||||||
|
SavingReadyPR: () => void = null;
|
||||||
private savingQueue: DirectoryDTO[] = [];
|
private savingQueue: DirectoryDTO[] = [];
|
||||||
private isSaving = false;
|
private isSaving = false;
|
||||||
|
|
||||||
|
get IsSavingInProgress() {
|
||||||
|
return this.SavingReady !== null;
|
||||||
|
}
|
||||||
|
|
||||||
public indexDirectory(relativeDirectoryName: string): Promise<DirectoryDTO> {
|
public indexDirectory(relativeDirectoryName: string): Promise<DirectoryDTO> {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
@ -67,10 +73,19 @@ export class IndexingManager implements IIndexingManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.savingQueue.push(scannedDirectory);
|
this.savingQueue.push(scannedDirectory);
|
||||||
|
if (!this.SavingReady) {
|
||||||
|
this.SavingReady = new Promise<void>((resolve) => {
|
||||||
|
this.SavingReadyPR = resolve;
|
||||||
|
});
|
||||||
|
}
|
||||||
while (this.isSaving === false && this.savingQueue.length > 0) {
|
while (this.isSaving === false && this.savingQueue.length > 0) {
|
||||||
await this.saveToDB(this.savingQueue[0]);
|
await this.saveToDB(this.savingQueue[0]);
|
||||||
this.savingQueue.shift();
|
this.savingQueue.shift();
|
||||||
}
|
}
|
||||||
|
if (this.savingQueue.length === 0) {
|
||||||
|
this.SavingReady = null;
|
||||||
|
this.SavingReadyPR();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,6 +106,7 @@ export class SQLConnection {
|
|||||||
this.connection = null;
|
this.connection = null;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error('Error during closing sql db:');
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,9 @@ export class IndexingJob extends Job {
|
|||||||
|
|
||||||
protected async step(): Promise<boolean> {
|
protected async step(): Promise<boolean> {
|
||||||
if (this.directoriesToIndex.length === 0) {
|
if (this.directoriesToIndex.length === 0) {
|
||||||
|
if (ObjectManagers.getInstance().IndexingManager.IsSavingInProgress) {
|
||||||
|
await ObjectManagers.getInstance().IndexingManager.SavingReady;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const directory = this.directoriesToIndex.shift();
|
const directory = this.directoriesToIndex.shift();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user