You've already forked pigallery2
							
							
				mirror of
				https://github.com/bpatrik/pigallery2.git
				synced 2025-10-30 23:57:43 +02:00 
			
		
		
		
	Adding benchmark to release code
This commit is contained in:
		| @@ -2,7 +2,6 @@ | ||||
| .idea/ | ||||
| .git/ | ||||
| node_modules/ | ||||
| benchmark/ | ||||
| release/ | ||||
| demo/ | ||||
| dist/ | ||||
|   | ||||
| @@ -11,6 +11,7 @@ import {ConfigProperty} from 'typeconfig/common'; | ||||
|   enumsAsString: true, | ||||
|   softReadonly: true, | ||||
|   cli: { | ||||
|     prefix: 'bm-config', | ||||
|     enable: { | ||||
|       configPath: true, | ||||
|       attachState: true, | ||||
| @@ -31,6 +32,8 @@ export class PrivateConfigClass { | ||||
|   path: string = 'demo/images'; | ||||
|   @ConfigProperty({description: 'Describe your system setup'}) | ||||
|   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. | ||||
|  | ||||
| ## 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 | ||||
|  | ||||
| **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 * as path from 'path'; | ||||
| import {ProjectPath} from '../src/backend/ProjectPath'; | ||||
| import {BenchmarkResult, Benchmarks} from './Benchmarks'; | ||||
| import {BenchmarkResult, BenchmarkRunner} from './BenchmarkRunner'; | ||||
| import {SearchTypes} from '../src/common/entities/AutoCompleteItem'; | ||||
| import {Utils} from '../src/common/Utils'; | ||||
| import {DiskMangerWorker} from '../src/backend/model/threading/DiskMangerWorker'; | ||||
| import {BMConfig} from './BMConfig'; | ||||
| import {GalleryManager} from '../src/backend/model/database/sql/GalleryManager'; | ||||
| import {PersonManager} from '../src/backend/model/database/sql/PersonManager'; | ||||
|  | ||||
|  | ||||
| Config.Server.Media.folder = BMConfig.path; | ||||
| const dbFolder = path.join(__dirname, './../'); | ||||
| ProjectPath.reset(); | ||||
| const RUNS = 50; | ||||
| const RUNS = BMConfig.RUNS; | ||||
|  | ||||
| let resultsText = ''; | ||||
| const printLine = (text: string) => { | ||||
| @@ -28,54 +27,75 @@ const printHeader = async () => { | ||||
|   printLine('**System**: ' + BMConfig.system); | ||||
|   const dir = await DiskMangerWorker.scanDirectory('./'); | ||||
|   const gm = new GalleryManager(); | ||||
|   printLine('**Gallery**: directories: ' + | ||||
|   const pm = new PersonManager(); | ||||
|   printLine('\n**Gallery**: directories: ' + | ||||
|     dir.directories.length + | ||||
|     ' media: ' + dir.media.length + | ||||
|     // @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 = () => { | ||||
|   printLine('| action | action details | average time | details |'); | ||||
|   printLine('|:------:|:--------------:|:------------:|:-------:|'); | ||||
|   printLine('| Action | Sub action | Action details | Average Duration | Details |'); | ||||
|   printLine('|:------:|:----------:|:--------------:|:----------------:|:-------:|'); | ||||
| }; | ||||
| const printResult = (result: BenchmarkResult, action: string, actionDetails: string = '') => { | ||||
|   console.log('benchmarked: ' + action); | ||||
| const printResult = (result: BenchmarkResult, actionDetails: string = '', isSubResult = false) => { | ||||
|   console.log('benchmarked: ' + result.name); | ||||
|   let details = '-'; | ||||
|   if (result.items) { | ||||
|     details = 'items: ' + result.items; | ||||
|   } | ||||
|   if (result.media) { | ||||
|     details = 'media: ' + result.media + ', directories:' + result.directories; | ||||
|   if (result.contentWrapper) { | ||||
|     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 () => { | ||||
|   console.log('Running, RUNS:' + RUNS); | ||||
|   const start = Date.now(); | ||||
|   const bm = new Benchmarks(RUNS); | ||||
|   const bm = new BenchmarkRunner(RUNS); | ||||
|  | ||||
|   // header | ||||
|   await printHeader(); | ||||
|   printTableHeader(); | ||||
|   printResult(await bm.bmScanDirectory(), 'Scanning directory'); | ||||
|   printResult(await bm.bmSaveDirectory(), 'Saving directory'); | ||||
|   printResult(await bm.bmListDirectory(), 'Listing Directory'); | ||||
|   printResult(await bm.bmListPersons(), 'Listing Faces'); | ||||
|  | ||||
|   printResult(await bm.bmScanDirectory()); | ||||
|   printResult(await bm.bmSaveDirectory()); | ||||
|   printResult(await bm.bmListDirectory()); | ||||
|   printResult(await bm.bmListPersons()); | ||||
|   (await bm.bmAllSearch('a')).forEach(res => { | ||||
|     if (res.searchType !== null) { | ||||
|       printResult(res.result, 'searching', '`a` as `' + SearchTypes[res.searchType] + '`'); | ||||
|       printResult(res.result, '`a` as `' + SearchTypes[res.searchType] + '`'); | ||||
|     } 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.bmAutocomplete('a'), 'auto complete', '`a`'); | ||||
|   printResult(await bm.bmInstantSearch('a'), '`a`'); | ||||
|   printResult(await bm.bmAutocomplete('a'), '`a`'); | ||||
|   printLine('*Measurements run ' + RUNS + ' times and an average was calculated.'); | ||||
|   console.log(resultsText); | ||||
|   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 () { | ||||
|   return gulp.src([ | ||||
|     'src/common/**/*.ts', | ||||
|     'src/backend/**/*.ts'], {base: '.'}) | ||||
|     'src/backend/**/*.ts', | ||||
|     'benchmark/**/*.ts'], {base: '.'}) | ||||
|     .pipe(tsBackendProject()) | ||||
|     .js | ||||
|     .pipe(gulp.dest('./release')); | ||||
|   | ||||
| @@ -1,6 +1,9 @@ | ||||
| import {DirectoryDTO} from '../../../../common/entities/DirectoryDTO'; | ||||
|  | ||||
| export interface IIndexingManager { | ||||
|   SavingReady: Promise<void> | ||||
|   IsSavingInProgress: boolean; | ||||
|  | ||||
|   indexDirectory(relativeDirectoryName: string): Promise<DirectoryDTO>; | ||||
|  | ||||
|   resetDB(): Promise<void>; | ||||
|   | ||||
| @@ -22,9 +22,15 @@ const LOG_TAG = '[IndexingManager]'; | ||||
|  | ||||
| export class IndexingManager implements IIndexingManager { | ||||
|  | ||||
|   SavingReady: Promise<void> = null; | ||||
|   SavingReadyPR: () => void = null; | ||||
|   private savingQueue: DirectoryDTO[] = []; | ||||
|   private isSaving = false; | ||||
|  | ||||
|   get IsSavingInProgress() { | ||||
|     return this.SavingReady !== null; | ||||
|   } | ||||
|  | ||||
|   public indexDirectory(relativeDirectoryName: string): Promise<DirectoryDTO> { | ||||
|     return new Promise(async (resolve, reject) => { | ||||
|       try { | ||||
| @@ -67,10 +73,19 @@ export class IndexingManager implements IIndexingManager { | ||||
|       return; | ||||
|     } | ||||
|     this.savingQueue.push(scannedDirectory); | ||||
|     if (!this.SavingReady) { | ||||
|       this.SavingReady = new Promise<void>((resolve) => { | ||||
|         this.SavingReadyPR = resolve; | ||||
|       }); | ||||
|     } | ||||
|     while (this.isSaving === false && this.savingQueue.length > 0) { | ||||
|       await this.saveToDB(this.savingQueue[0]); | ||||
|       this.savingQueue.shift(); | ||||
|     } | ||||
|     if (this.savingQueue.length === 0) { | ||||
|       this.SavingReady = null; | ||||
|       this.SavingReadyPR(); | ||||
|     } | ||||
|  | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -106,6 +106,7 @@ export class SQLConnection { | ||||
|         this.connection = null; | ||||
|       } | ||||
|     } catch (err) { | ||||
|       console.error('Error during closing sql db:'); | ||||
|       console.error(err); | ||||
|     } | ||||
|   } | ||||
|   | ||||
| @@ -23,6 +23,9 @@ export class IndexingJob extends Job { | ||||
|  | ||||
|   protected async step(): Promise<boolean> { | ||||
|     if (this.directoriesToIndex.length === 0) { | ||||
|       if (ObjectManagers.getInstance().IndexingManager.IsSavingInProgress) { | ||||
|         await ObjectManagers.getInstance().IndexingManager.SavingReady; | ||||
|       } | ||||
|       return false; | ||||
|     } | ||||
|     const directory = this.directoriesToIndex.shift(); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user