@dollhousemcp/mcp-server
Version:
DollhouseMCP - A Model Context Protocol (MCP) server that enables dynamic AI persona management from markdown files, allowing Claude and other compatible AI assistants to activate and switch between different behavioral personas.
535 lines • 71.7 kB
JavaScript
/**
* Comprehensive Performance Benchmarking Suite for DollhouseMCP Indexing System
*
* Benchmarks:
* - Search response times under various loads
* - Memory usage patterns with large datasets
* - Cache performance and hit rates
* - Concurrent operation performance
* - Index building and rebuilding times
*
* Note: Some metrics are collected but not yet used in reporting.
* These are kept for future dashboard/analytics implementation.
*/
/* eslint-disable @typescript-eslint/no-unused-vars */
import { DollhouseContainer } from '../di/Container.js';
import { logger } from '../utils/logger.js';
import { UnicodeValidator } from '../security/validators/unicodeValidator.js';
export class IndexPerformanceBenchmark {
unifiedIndexManager;
performanceMonitor;
benchmarkResults = [];
container;
constructor() {
this.container = new DollhouseContainer();
this.unifiedIndexManager = this.container.resolve('UnifiedIndexManager');
this.performanceMonitor = this.container.resolve('PerformanceMonitor');
}
/**
* Run comprehensive performance benchmark suite
*/
async runFullBenchmarkSuite() {
logger.info('Starting comprehensive performance benchmark suite');
const suiteStartTime = Date.now();
// Clear caches and reset state
await this.resetBenchmarkEnvironment();
const benchmarks = [
() => this.benchmarkSearchPerformance(),
() => this.benchmarkMemoryUsage(),
() => this.benchmarkCachePerformance(),
() => this.benchmarkConcurrentOperations(),
() => this.benchmarkLargeDatasetHandling(),
() => this.benchmarkIndexBuilding(),
() => this.benchmarkStreamingSearch(),
() => this.benchmarkLazyLoading()
];
// Run each benchmark
for (const benchmark of benchmarks) {
try {
const result = await benchmark();
this.benchmarkResults.push(result);
// Allow garbage collection between benchmarks
await this.waitForGC();
}
catch (error) {
logger.error('Benchmark failed', {
error: error instanceof Error ? error.message : String(error)
});
}
}
const suite = {
name: 'DollhouseMCP Index Performance Suite',
results: this.benchmarkResults,
summary: this.generateSummary(Date.now() - suiteStartTime)
};
logger.info('Benchmark suite completed', {
totalBenchmarks: suite.results.length,
totalDuration: suite.summary.totalDuration,
recommendations: suite.summary.recommendations.length
});
return suite;
}
/**
* Benchmark search performance with various query patterns
*/
async benchmarkSearchPerformance() {
const testQueries = [
'creative',
'professional assistant',
'development tools',
'data analysis',
'machine learning expert',
'a very long and complex query that might stress the search system with multiple terms and complex patterns',
'specific_exact_match',
'' // Empty query test
];
const memoryBefore = process.memoryUsage().heapUsed;
let peakMemory = memoryBefore;
const startTime = Date.now();
let totalOperations = 0;
let cacheHits = 0;
for (const query of testQueries) {
for (let i = 0; i < 10; i++) { // 10 iterations per query
const options = {
query,
includeLocal: true,
includeGitHub: true,
includeCollection: false,
pageSize: 20
};
await this.unifiedIndexManager.search(options);
totalOperations++;
// Track peak memory
const currentMemory = process.memoryUsage().heapUsed;
if (currentMemory > peakMemory) {
peakMemory = currentMemory;
}
}
}
const endTime = Date.now();
const memoryAfter = process.memoryUsage().heapUsed;
const duration = endTime - startTime;
const performanceStats = this.unifiedIndexManager.getPerformanceStats();
const searchStats = performanceStats.searchStats;
return {
name: 'Search Performance',
duration,
memoryUsage: {
before: memoryBefore / (1024 * 1024),
after: memoryAfter / (1024 * 1024),
peak: peakMemory / (1024 * 1024)
},
throughput: totalOperations / (duration / 1000),
cacheStats: {
hitRate: searchStats.cacheHitRate || 0,
totalOperations
},
metadata: {
avgSearchTime: searchStats.averageTime || 0,
p95SearchTime: searchStats.p95Time || 0,
slowQueries: searchStats.slowQueries || 0
}
};
}
/**
* Benchmark memory usage with large result sets
*/
async benchmarkMemoryUsage() {
const memoryBefore = process.memoryUsage().heapUsed;
let peakMemory = memoryBefore;
const startTime = Date.now();
// Create increasingly large search operations
const largeBatches = [100, 500, 1000, 2000];
for (const batchSize of largeBatches) {
const options = {
query: '', // Empty query to get all results
includeLocal: true,
includeGitHub: true,
includeCollection: true,
pageSize: batchSize,
maxResults: batchSize
};
await this.unifiedIndexManager.search(options);
const currentMemory = process.memoryUsage().heapUsed;
if (currentMemory > peakMemory) {
peakMemory = currentMemory;
}
// Check for memory leaks
if (currentMemory > memoryBefore * 2) {
logger.warn('Potential memory leak detected', {
beforeMB: memoryBefore / (1024 * 1024),
currentMB: currentMemory / (1024 * 1024),
batchSize
});
}
}
const endTime = Date.now();
const memoryAfter = process.memoryUsage().heapUsed;
return {
name: 'Memory Usage',
duration: endTime - startTime,
memoryUsage: {
before: memoryBefore / (1024 * 1024),
after: memoryAfter / (1024 * 1024),
peak: peakMemory / (1024 * 1024)
},
metadata: {
memoryGrowthMB: (memoryAfter - memoryBefore) / (1024 * 1024),
peakGrowthMB: (peakMemory - memoryBefore) / (1024 * 1024),
testedBatchSizes: largeBatches
}
};
}
/**
* Benchmark cache performance and hit rates
*/
async benchmarkCachePerformance() {
const memoryBefore = process.memoryUsage().heapUsed;
const startTime = Date.now();
// Test cache warming and hit rate optimization
const rawQueries = ['test', 'performance', 'benchmark', 'cache'];
// DMCP-SEC-004 FIX: Normalize Unicode in all user input
const testQueries = rawQueries.map(q => {
const normalized = UnicodeValidator.normalize(q);
return normalized.isValid ? normalized.normalizedContent : q;
});
let totalOperations = 0;
let cacheHitsBefore = 0;
// First pass - cache misses expected
for (const query of testQueries) {
for (let i = 0; i < 5; i++) {
await this.unifiedIndexManager.search({ query, pageSize: 10 });
totalOperations++;
}
}
const statsAfterFirstPass = this.unifiedIndexManager.getPerformanceStats();
cacheHitsBefore = statsAfterFirstPass.cacheStats.searchResults.hitCount;
// Second pass - cache hits expected
for (const query of testQueries) {
for (let i = 0; i < 5; i++) {
await this.unifiedIndexManager.search({ query, pageSize: 10 });
totalOperations++;
}
}
const endTime = Date.now();
const memoryAfter = process.memoryUsage().heapUsed;
const finalStats = this.unifiedIndexManager.getPerformanceStats();
const cacheStats = finalStats.cacheStats.searchResults;
return {
name: 'Cache Performance',
duration: endTime - startTime,
memoryUsage: {
before: memoryBefore / (1024 * 1024),
after: memoryAfter / (1024 * 1024),
peak: memoryAfter / (1024 * 1024)
},
cacheStats: {
hitRate: cacheStats.hitRate,
totalOperations
},
metadata: {
cacheSize: cacheStats.size,
evictions: cacheStats.evictionCount,
hitRateImprovement: cacheStats.hitRate
}
};
}
/**
* Benchmark concurrent search operations
*/
async benchmarkConcurrentOperations() {
const memoryBefore = process.memoryUsage().heapUsed;
let peakMemory = memoryBefore;
const startTime = Date.now();
const concurrencyLevels = [5, 10, 20, 50];
let totalOperations = 0;
for (const concurrency of concurrencyLevels) {
const promises = [];
for (let i = 0; i < concurrency; i++) {
const searchPromise = this.unifiedIndexManager.search({
query: `concurrent_test_${i}`,
pageSize: 20
});
promises.push(searchPromise);
totalOperations++;
}
await Promise.all(promises);
const currentMemory = process.memoryUsage().heapUsed;
if (currentMemory > peakMemory) {
peakMemory = currentMemory;
}
}
const endTime = Date.now();
const memoryAfter = process.memoryUsage().heapUsed;
const duration = endTime - startTime;
return {
name: 'Concurrent Operations',
duration,
memoryUsage: {
before: memoryBefore / (1024 * 1024),
after: memoryAfter / (1024 * 1024),
peak: peakMemory / (1024 * 1024)
},
throughput: totalOperations / (duration / 1000),
metadata: {
testedConcurrencyLevels: concurrencyLevels,
totalConcurrentOps: totalOperations
}
};
}
/**
* Benchmark large dataset handling (simulated)
*/
async benchmarkLargeDatasetHandling() {
const memoryBefore = process.memoryUsage().heapUsed;
let peakMemory = memoryBefore;
const startTime = Date.now();
// Simulate searches that would stress a large dataset
const stressTestQueries = [
{ query: '', pageSize: 1000 }, // Get all results
{ query: 'common_term', pageSize: 500 }, // High result count
{ query: 'very_specific_unique_term', pageSize: 100 }, // Low result count
{ query: 'partial_match_test', pageSize: 200 } // Medium result count
];
let totalResults = 0;
for (const testCase of stressTestQueries) {
const results = await this.unifiedIndexManager.search({
query: testCase.query,
pageSize: testCase.pageSize,
includeLocal: true,
includeGitHub: true,
includeCollection: true
});
totalResults += results.length;
const currentMemory = process.memoryUsage().heapUsed;
if (currentMemory > peakMemory) {
peakMemory = currentMemory;
}
}
const endTime = Date.now();
const memoryAfter = process.memoryUsage().heapUsed;
return {
name: 'Large Dataset Handling',
duration: endTime - startTime,
memoryUsage: {
before: memoryBefore / (1024 * 1024),
after: memoryAfter / (1024 * 1024),
peak: peakMemory / (1024 * 1024)
},
metadata: {
totalResultsProcessed: totalResults,
averageResultsPerQuery: totalResults / stressTestQueries.length,
memoryPerResult: (peakMemory - memoryBefore) / totalResults
}
};
}
/**
* Benchmark index building performance
*/
async benchmarkIndexBuilding() {
const memoryBefore = process.memoryUsage().heapUsed;
const startTime = Date.now();
// Force rebuild of all indexes
await this.unifiedIndexManager.rebuildAll();
const endTime = Date.now();
const memoryAfter = process.memoryUsage().heapUsed;
const localStats = await this.container.resolve('PortfolioIndexManager').getStats();
return {
name: 'Index Building',
duration: endTime - startTime,
memoryUsage: {
before: memoryBefore / (1024 * 1024),
after: memoryAfter / (1024 * 1024),
peak: memoryAfter / (1024 * 1024)
},
metadata: {
totalElementsIndexed: localStats.totalElements,
indexingRate: localStats.totalElements / ((endTime - startTime) / 1000),
isStale: localStats.isStale
}
};
}
/**
* Benchmark streaming search performance
*/
async benchmarkStreamingSearch() {
const memoryBefore = process.memoryUsage().heapUsed;
let peakMemory = memoryBefore;
const startTime = Date.now();
const streamingQueries = [
{ query: 'test', maxResults: 100 },
{ query: 'assistant', maxResults: 200 },
{ query: 'creative', maxResults: 150 }
];
let totalResults = 0;
for (const testCase of streamingQueries) {
const results = await this.unifiedIndexManager.search({
query: testCase.query,
streamResults: true,
maxResults: testCase.maxResults,
includeLocal: true,
includeGitHub: true
});
totalResults += results.length;
const currentMemory = process.memoryUsage().heapUsed;
if (currentMemory > peakMemory) {
peakMemory = currentMemory;
}
}
const endTime = Date.now();
const memoryAfter = process.memoryUsage().heapUsed;
return {
name: 'Streaming Search',
duration: endTime - startTime,
memoryUsage: {
before: memoryBefore / (1024 * 1024),
after: memoryAfter / (1024 * 1024),
peak: peakMemory / (1024 * 1024)
},
throughput: totalResults / ((endTime - startTime) / 1000),
metadata: {
totalStreamedResults: totalResults,
averageMemoryPerResult: (peakMemory - memoryBefore) / totalResults
}
};
}
/**
* Benchmark lazy loading performance
*/
async benchmarkLazyLoading() {
const memoryBefore = process.memoryUsage().heapUsed;
const startTime = Date.now();
// Test lazy loading with different configurations
const lazyLoadTests = [
{ query: 'lazy_test_1', lazyLoad: true, includeLocal: true, includeGitHub: false, includeCollection: false },
{ query: 'lazy_test_2', lazyLoad: true, includeLocal: false, includeGitHub: true, includeCollection: false },
{ query: 'lazy_test_3', lazyLoad: true, includeLocal: true, includeGitHub: true, includeCollection: true },
{ query: 'lazy_test_4', lazyLoad: false, includeLocal: true, includeGitHub: true, includeCollection: true }
];
let totalOperations = 0;
for (const testCase of lazyLoadTests) {
await this.unifiedIndexManager.search(testCase);
totalOperations++;
}
const endTime = Date.now();
const memoryAfter = process.memoryUsage().heapUsed;
return {
name: 'Lazy Loading',
duration: endTime - startTime,
memoryUsage: {
before: memoryBefore / (1024 * 1024),
after: memoryAfter / (1024 * 1024),
peak: memoryAfter / (1024 * 1024)
},
throughput: totalOperations / ((endTime - startTime) / 1000),
metadata: {
totalLazyOperations: totalOperations,
averageOperationTime: (endTime - startTime) / totalOperations
}
};
}
/**
* Reset benchmark environment
*/
async resetBenchmarkEnvironment() {
await this.unifiedIndexManager.rebuildAll();
this.performanceMonitor.reset();
this.benchmarkResults = [];
// Force garbage collection if available
if (global.gc) {
global.gc();
}
await this.waitForGC();
}
/**
* Wait for garbage collection to complete
*/
async waitForGC() {
return new Promise(resolve => {
setImmediate(() => {
if (global.gc) {
global.gc();
}
setTimeout(resolve, 100); // Wait 100ms for GC to complete
});
});
}
/**
* Generate benchmark summary and recommendations
*/
generateSummary(totalDuration) {
const recommendations = [];
let totalMemoryUsage = 0;
let peakMemoryUsage = 0;
// Analyze results and generate recommendations
for (const result of this.benchmarkResults) {
totalMemoryUsage += result.memoryUsage.after - result.memoryUsage.before;
peakMemoryUsage = Math.max(peakMemoryUsage, result.memoryUsage.peak);
// Check for performance issues
if (result.name === 'Search Performance' && result.metadata?.avgSearchTime > 100) {
recommendations.push('Average search time exceeds 100ms. Consider query optimization or increased caching.');
}
if (result.memoryUsage.peak > 200) {
recommendations.push(`High memory usage detected in ${result.name} (${result.memoryUsage.peak.toFixed(1)}MB). Consider memory optimization.`);
}
if (result.cacheStats?.hitRate && result.cacheStats.hitRate < 0.5) {
recommendations.push(`Low cache hit rate in ${result.name} (${result.cacheStats.hitRate.toFixed(2)}). Consider cache size increase or TTL adjustment.`);
}
if (result.throughput && result.throughput < 10) {
recommendations.push(`Low throughput in ${result.name} (${result.throughput.toFixed(1)} ops/sec). Consider performance optimization.`);
}
}
// General recommendations
if (peakMemoryUsage > 50) {
recommendations.push('Peak memory usage exceeds 50MB. Consider implementing more aggressive memory cleanup.');
}
if (totalDuration > 30000) {
recommendations.push('Total benchmark duration exceeds 30 seconds. Consider performance optimizations.');
}
return {
totalDuration,
averageMemoryUsage: totalMemoryUsage / this.benchmarkResults.length,
peakMemoryUsage,
recommendations
};
}
/**
* Export benchmark results to JSON
*/
exportResults(suite) {
return JSON.stringify(suite, null, 2);
}
/**
* Generate benchmark report
*/
generateReport(suite) {
let report = `# DollhouseMCP Index Performance Benchmark Report\n\n`;
report += `**Generated:** ${new Date().toISOString()}\n`;
report += `**Total Duration:** ${suite.summary.totalDuration}ms\n`;
report += `**Peak Memory Usage:** ${suite.summary.peakMemoryUsage.toFixed(1)}MB\n\n`;
report += `## Benchmark Results\n\n`;
for (const result of suite.results) {
report += `### ${result.name}\n`;
report += `- **Duration:** ${result.duration}ms\n`;
report += `- **Memory Usage:** ${result.memoryUsage.before.toFixed(1)}MB → ${result.memoryUsage.after.toFixed(1)}MB (Peak: ${result.memoryUsage.peak.toFixed(1)}MB)\n`;
if (result.throughput) {
report += `- **Throughput:** ${result.throughput.toFixed(1)} ops/sec\n`;
}
if (result.cacheStats) {
report += `- **Cache Hit Rate:** ${result.cacheStats.hitRate.toFixed(2)}\n`;
}
report += `\n`;
}
report += `## Recommendations\n\n`;
if (suite.summary.recommendations.length === 0) {
report += `No performance issues detected. System is performing within acceptable parameters.\n`;
}
else {
for (const recommendation of suite.summary.recommendations) {
report += `- ${recommendation}\n`;
}
}
return report;
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiSW5kZXhQZXJmb3JtYW5jZUJlbmNobWFyay5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9iZW5jaG1hcmtzL0luZGV4UGVyZm9ybWFuY2VCZW5jaG1hcmsudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7OztHQVlHO0FBQ0gsc0RBQXNEO0FBSXRELE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBRXhELE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUM1QyxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSw0Q0FBNEMsQ0FBQztBQTZCOUUsTUFBTSxPQUFPLHlCQUF5QjtJQUM1QixtQkFBbUIsQ0FBc0I7SUFDekMsa0JBQWtCLENBQXFCO0lBQ3ZDLGdCQUFnQixHQUFzQixFQUFFLENBQUM7SUFDekMsU0FBUyxDQUFxQjtJQUV0QztRQUNFLElBQUksQ0FBQyxTQUFTLEdBQUcsSUFBSSxrQkFBa0IsRUFBRSxDQUFDO1FBQzFDLElBQUksQ0FBQyxtQkFBbUIsR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLE9BQU8sQ0FBQyxxQkFBcUIsQ0FBQyxDQUFDO1FBQ3pFLElBQUksQ0FBQyxrQkFBa0IsR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLE9BQU8sQ0FBQyxvQkFBb0IsQ0FBQyxDQUFDO0lBQ3pFLENBQUM7SUFFRDs7T0FFRztJQUNILEtBQUssQ0FBQyxxQkFBcUI7UUFDekIsTUFBTSxDQUFDLElBQUksQ0FBQyxvREFBb0QsQ0FBQyxDQUFDO1FBQ2xFLE1BQU0sY0FBYyxHQUFHLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztRQUVsQywrQkFBK0I7UUFDL0IsTUFBTSxJQUFJLENBQUMseUJBQXlCLEVBQUUsQ0FBQztRQUV2QyxNQUFNLFVBQVUsR0FBRztZQUNqQixHQUFHLEVBQUUsQ0FBQyxJQUFJLENBQUMsMEJBQTBCLEVBQUU7WUFDdkMsR0FBRyxFQUFFLENBQUMs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SyxDQUFDLEVBQUUsQ0FBQztZQUMvQyxNQUFNLElBQUksc0ZBQXNGLENBQUM7UUFDbkcsQ0FBQzthQUFNLENBQUM7WUFDTixLQUFLLE1BQU0sY0FBYyxJQUFJLEtBQUssQ0FBQyxPQUFPLENBQUMsZUFBZSxFQUFFLENBQUM7Z0JBQzNELE1BQU0sSUFBSSxLQUFLLGNBQWMsSUFBSSxDQUFDO1lBQ3BDLENBQUM7UUFDSCxDQUFDO1FBRUQsT0FBTyxNQUFNLENBQUM7SUFDaEIsQ0FBQztDQUNGIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBDb21wcmVoZW5zaXZlIFBlcmZvcm1hbmNlIEJlbmNobWFya2luZyBTdWl0ZSBmb3IgRG9sbGhvdXNlTUNQIEluZGV4aW5nIFN5c3RlbVxuICpcbiAqIEJlbmNobWFya3M6XG4gKiAtIFNlYXJjaCByZXNwb25zZSB0aW1lcyB1bmRlciB2YXJpb3VzIGxvYWRzXG4gKiAtIE1lbW9yeSB1c2FnZSBwYXR0ZXJucyB3aXRoIGxhcmdlIGRhdGFzZXRzXG4gKiAtIENhY2hlIHBlcmZvcm1hbmNlIGFuZCBoaXQgcmF0ZXNcbiAqIC0gQ29uY3VycmVudCBvcGVyYXRpb24gcGVyZm9ybWFuY2VcbiAqIC0gSW5kZXggYnVpbGRpbmcgYW5kIHJlYnVpbGRpbmcgdGltZXNcbiAqXG4gKiBOb3RlOiBTb21lIG1ldHJpY3MgYXJlIGNvbGxlY3RlZCBidXQgbm90IHlldCB1c2VkIGluIHJlcG9ydGluZy5cbiAqIFRoZXNlIGFyZSBrZXB0IGZvciBmdXR1cmUgZGFzaGJvYXJkL2FuYWx5dGljcyBpbXBsZW1lbnRhdGlvbi5cbiAqL1xuLyogZXNsaW50LWRpc2FibGUgQHR5cGVzY3JpcHQtZXNsaW50L25vLXVudXNlZC12YXJzICovXG5cbmltcG9ydCB7IFVuaWZpZWRJbmRleE1hbmFnZXIsIFVuaWZpZWRTZWFyY2hPcHRpb25zIH0gZnJvbSAnLi4vcG9ydGZvbGlvL1VuaWZpZWRJbmRleE1hbmFnZXIuanMnO1xuaW1wb3J0IHsgUG9ydGZvbGlvSW5kZXhNYW5hZ2VyIH0gZnJvbSAnLi4vcG9ydGZvbGlvL1BvcnRmb2xpb0luZGV4TWFuYWdlci5qcyc7XG5pbXBvcnQgeyBEb2xsaG91c2VDb250YWluZXIgfSBmcm9tICcuLi9kaS9Db250YWluZXIuanMnO1xuaW1wb3J0IHsgUGVyZm9ybWFuY2VNb25pdG9yIH0gZnJvbSAnLi4vdXRpbHMvUGVyZm9ybWFuY2VNb25pdG9yLmpzJztcbmltcG9ydCB7IGxvZ2dlciB9IGZyb20gJy4uL3V0aWxzL2xvZ2dlci5qcyc7XG5pbXBvcnQgeyBVbmljb2RlVmFsaWRhdG9yIH0gZnJvbSAnLi4vc2VjdXJpdHkvdmFsaWRhdG9ycy91bmljb2RlVmFsaWRhdG9yLmpzJztcblxuZXhwb3J0IGludGVyZmFjZSBCZW5jaG1hcmtSZXN1bHQge1xuICBuYW1lOiBzdHJpbmc7XG4gIGR1cmF0aW9uOiBudW1iZXI7XG4gIG1lbW9yeVVzYWdlOiB7XG4gICAgYmVmb3JlOiBudW1iZXI7XG4gICAgYWZ0ZXI6IG51bWJlcjtcbiAgICBwZWFrOiBudW1iZXI7XG4gIH07XG4gIHRocm91Z2hwdXQ/OiBudW1iZXI7IC8vIG9wZXJhdGlvbnMgcGVyIHNlY29uZFxuICBjYWNoZVN0YXRzPzoge1xuICAgIGhpdFJhdGU6IG51bWJlcjtcbiAgICB0b3RhbE9wZXJhdGlvbnM6IG51bWJlcjtcbiAgfTtcbiAgbWV0YWRhdGE/OiBhbnk7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgQmVuY2htYXJrU3VpdGUge1xuICBuYW1lOiBzdHJpbmc7XG4gIHJlc3VsdHM6IEJlbmNobWFya1Jlc3VsdFtdO1xuICBzdW1tYXJ5OiB7XG4gICAgdG90YWxEdXJhdGlvbjogbnVtYmVyO1xuICAgIGF2ZXJhZ2VNZW1vcnlVc2FnZTogbnVtYmVyO1xuICAgIHBlYWtNZW1vcnlVc2FnZTogbnVtYmVyO1xuICAgIHJlY29tbWVuZGF0aW9uczogc3RyaW5nW107XG4gIH07XG59XG5cbmV4cG9ydCBjbGFzcyBJbmRleFBlcmZvcm1hbmNlQmVuY2htYXJrIHtcbiAgcHJpdmF0ZSB1bmlmaWVkSW5kZXhNYW5hZ2VyOiBVbmlmaWVkSW5kZXhNYW5hZ2VyO1xuICBwcml2YXRlIHBlcmZvcm1hbmNlTW9uaXRvcjogUGVyZm9ybWFuY2VNb25pdG9yO1xuICBwcml2YXRlIGJlbmNobWFya1Jlc3VsdHM6IEJlbmNobWFya1Jlc3VsdFtdID0gW107XG4gIHByaXZhdGUgY29udGFpbmVyOiBEb2xsaG91c2VDb250YWluZXI7XG5cbiAgY29uc3RydWN0b3IoKSB7XG4gICAgdGhpcy5jb250YWluZXIgPSBuZXcgRG9sbGhvdXNlQ29udGFpbmVyKCk7XG4gICAgdGhpcy51bmlmaWVkSW5kZXhNYW5hZ2VyID0gdGhpcy5jb250YWluZXIucmVzb2x2ZSgnVW5pZmllZEluZGV4TWFuYWdlcicpO1xuICAgIHRoaXMucGVyZm9ybWFuY2VNb25pdG9yID0gdGhpcy5jb250YWluZXIucmVzb2x2ZSgnUGVyZm9ybWFuY2VNb25pdG9yJyk7XG4gIH1cblxuICAvKipcbiAgICogUnVuIGNvbXByZWhlbnNpdmUgcGVyZm9ybWFuY2UgYmVuY2htYXJrIHN1aXRlXG4gICAqL1xuICBhc3luYyBydW5GdWxsQmVuY2htYXJrU3VpdGUoKTogUHJvbWlzZTxCZW5jaG1hcmtTdWl0ZT4ge1xuICAgIGxvZ2dlci5pbmZvKCdTdGFydGluZyBjb21wcmVoZW5zaXZlIHBlcmZvcm1hbmNlIGJlbmNobWFyayBzdWl0ZScpO1xuICAgIGNvbnN0IHN1aXRlU3RhcnRUaW1lID0gRGF0ZS5ub3coKTtcblxuICAgIC8vIENsZWFyIGNhY2hlcyBhbmQgcmVzZXQgc3RhdGVcbiAgICBhd2FpdCB0aGlzLnJlc2V0QmVuY2htYXJrRW52aXJvbm1lbnQoKTtcblxuICAgIGNvbnN0IGJlbmNobWFya3MgPSBbXG4gICAgICAoKSA9PiB0aGlzLmJlbmNobWFya1NlYXJjaFBlcmZvcm1hbmNlKCksXG4gICAgICAoKSA9PiB0aGlzLmJlbmNobWFya01lbW9yeVVzYWdlKCksXG4gICAgICAoKSA9PiB0aGlzLmJlbmNobWFya0NhY2hlUGVyZm9ybWFuY2UoKSxcbiAgICAgICgpID0+IHRoaXMuYmVuY2htYXJrQ29uY3VycmVudE9wZXJhdGlvbnMoKSxcbiAgICAgICgpID0+IHRoaXMuYmVuY2htYXJrTGFyZ2VEYXRhc2V0SGFuZGxpbmcoKSxcbiAgICAgICgpID0+IHRoaXMuYmVuY2htYXJrSW5kZXhCdWlsZGluZygpLFxuICAgICAgKCkgPT4gdGhpcy5iZW5jaG1hcmtTdHJlYW1pbmdTZWFyY2goKSxcbiAgICAgICgpID0+IHRoaXMuYmVuY2htYXJrTGF6eUxvYWRpbmcoKVxuICAgIF07XG5cbiAgICAvLyBSdW4gZWFjaCBiZW5jaG1hcmtcbiAgICBmb3IgKGNvbnN0IGJlbmNobWFyayBvZiBiZW5jaG1hcmtzKSB7XG4gICAgICB0cnkge1xuICAgICAgICBjb25zdCByZXN1bHQgPSBhd2FpdCBiZW5jaG1hcmsoKTtcbiAgICAgICAgdGhpcy5iZW5jaG1hcmtSZXN1bHRzLnB1c2gocmVzdWx0KTtcbiAgICAgICAgXG4gICAgICAgIC8vIEFsbG93IGdhcmJhZ2UgY29sbGVjdGlvbiBiZXR3ZWVuIGJlbmNobWFya3NcbiAgICAgICAgYXdhaXQgdGhpcy53YWl0Rm9yR0MoKTtcbiAgICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgIGxvZ2dlci5lcnJvcignQmVuY2htYXJrIGZhaWxlZCcsIHtcbiAgICAgICAgICBlcnJvcjogZXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGVycm9yLm1lc3NhZ2UgOiBTdHJpbmcoZXJyb3IpXG4gICAgICAgIH0pO1xuICAgICAgfVxuICAgIH1cblxuICAgIGNvbnN0IHN1aXRlOiBCZW5jaG1hcmtTdWl0ZSA9IHtcbiAgICAgIG5hbWU6ICdEb2xsaG91c2VNQ1AgSW5kZXggUGVyZm9ybWFuY2UgU3VpdGUnLFxuICAgICAgcmVzdWx0czogdGhpcy5iZW5jaG1hcmtSZXN1bHRzLFxuICAgICAgc3VtbWFyeTogdGhpcy5nZW5lcmF0ZVN1bW1hcnkoRGF0ZS5ub3coKSAtIHN1aXRlU3RhcnRUaW1lKVxuICAgIH07XG5cbiAgICBsb2dnZXIuaW5mbygnQmVuY2htYXJrIHN1aXRlIGNvbXBsZXRlZCcsIHtcbiAgICAgIHRvdGFsQmVuY2htYXJrczogc3VpdGUucmVzdWx0cy5sZW5ndGgsXG4gICAgICB0b3RhbER1cmF0aW9uOiBzdWl0ZS5zdW1tYXJ5LnRvdGFsRHVyYXRpb24sXG4gICAgICByZWNvbW1lbmRhdGlvbnM6IHN1aXRlLnN1bW1hcnkucmVjb21tZW5kYXRpb25zLmxlbmd0aFxuICAgIH0pO1xuXG4gICAgcmV0dXJuIHN1aXRlO1xuICB9XG5cbiAgLyoqXG4gICAqIEJlbmNobWFyayBzZWFyY2ggcGVyZm9ybWFuY2Ugd2l0aCB2YXJpb3VzIHF1ZXJ5IHBhdHRlcm5zXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIGJlbmNobWFya1NlYXJjaFBlcmZvcm1hbmNlKCk6IFByb21pc2U8QmVuY2htYXJrUmVzdWx0PiB7XG4gICAgY29uc3QgdGVzdFF1ZXJpZXMgPSBbXG4gICAgICAnY3JlYXRpdmUnLFxuICAgICAgJ3Byb2Zlc3Npb25hbCBhc3Npc3RhbnQnLFxuICAgICAgJ2RldmVsb3BtZW50IHRvb2xzJyxcbiAgICAgICdkYXRhIGFuYWx5c2lzJyxcbiAgICAgICdtYWNoaW5lIGxlYXJuaW5nIGV4cGVydCcsXG4gICAgICAnYSB2ZXJ5IGxvbmcgYW5kIGNvbXBsZXggcXVlcnkgdGhhdCBtaWdodCBzdHJlc3MgdGhlIHNlYXJjaCBzeXN0ZW0gd2l0aCBtdWx0aXBsZSB0ZXJtcyBhbmQgY29tcGxleCBwYXR0ZXJucycsXG4gICAgICAnc3BlY2lmaWNfZXhhY3RfbWF0Y2gnLFxuICAgICAgJycgIC8vIEVtcHR5IHF1ZXJ5IHRlc3RcbiAgICBdO1xuXG4gICAgY29uc3QgbWVtb3J5QmVmb3JlID0gcHJvY2Vzcy5tZW1vcnlVc2FnZSgpLmhlYXBVc2VkO1xuICAgIGxldCBwZWFrTWVtb3J5ID0gbWVtb3J5QmVmb3JlO1xuICAgIGNvbnN0IHN0YXJ0VGltZSA9IERhdGUubm93KCk7XG5cbiAgICBsZXQgdG90YWxPcGVyYXRpb25zID0gMDtcbiAgICBsZXQgY2FjaGVIaXRzID0gMDtcblxuICAgIGZvciAoY29uc3QgcXVlcnkgb2YgdGVzdFF1ZXJpZXMpIHtcbiAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgMTA7IGkrKykgeyAvLyAxMCBpdGVyYXRpb25zIHBlciBxdWVyeVxuICAgICAgICBjb25zdCBvcHRpb25zOiBVbmlmaWVkU2VhcmNoT3B0aW9ucyA9IHtcbiAgICAgICAgICBxdWVyeSxcbiAgICAgICAgICBpbmNsdWRlTG9jYWw6IHRydWUsXG4gICAgICAgICAgaW5jbHVkZUdpdEh1YjogdHJ1ZSxcbiAgICAgICAgICBpbmNsdWRlQ29sbGVjdGlvbjogZmFsc2UsXG4gICAgICAgICAgcGFnZVNpemU6IDIwXG4gICAgICAgIH07XG5cbiAgICAgICAgYXdhaXQgdGhpcy51bmlmaWVkSW5kZXhNYW5hZ2VyLnNlYXJjaChvcHRpb25zKTtcbiAgICAgICAgdG90YWxPcGVyYXRpb25zKys7XG5cbiAgICAgICAgLy8gVHJhY2sgcGVhayBtZW1vcnlcbiAgICAgICAgY29uc3QgY3VycmVudE1lbW9yeSA9IHByb2Nlc3MubWVtb3J5VXNhZ2UoKS5oZWFwVXNlZDtcbiAgICAgICAgaWYgKGN1cnJlbnRNZW1vcnkgPiBwZWFrTWVtb3J5KSB7XG4gICAgIC