276 lines
12 KiB
JavaScript
276 lines
12 KiB
JavaScript
|
|
/**
|
||
|
|
* Utility CLI Commands - TypeScript Implementation
|
||
|
|
*
|
||
|
|
* Database maintenance, statistics, and benchmarking
|
||
|
|
*/
|
||
|
|
import chalk from 'chalk';
|
||
|
|
import ora from 'ora';
|
||
|
|
import Table from 'cli-table3';
|
||
|
|
import { BrainyData } from '../../brainyData.js';
|
||
|
|
let brainyInstance = null;
|
||
|
|
const getBrainy = async () => {
|
||
|
|
if (!brainyInstance) {
|
||
|
|
brainyInstance = new BrainyData();
|
||
|
|
await brainyInstance.init();
|
||
|
|
}
|
||
|
|
return brainyInstance;
|
||
|
|
};
|
||
|
|
const formatBytes = (bytes) => {
|
||
|
|
if (bytes === 0)
|
||
|
|
return '0 B';
|
||
|
|
const k = 1024;
|
||
|
|
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||
|
|
};
|
||
|
|
const formatOutput = (data, options) => {
|
||
|
|
if (options.json) {
|
||
|
|
console.log(options.pretty ? JSON.stringify(data, null, 2) : JSON.stringify(data));
|
||
|
|
}
|
||
|
|
};
|
||
|
|
export const utilityCommands = {
|
||
|
|
/**
|
||
|
|
* Show database statistics
|
||
|
|
*/
|
||
|
|
async stats(options) {
|
||
|
|
const spinner = ora('Gathering statistics...').start();
|
||
|
|
try {
|
||
|
|
const brain = await getBrainy();
|
||
|
|
const stats = await brain.getStatistics();
|
||
|
|
const memUsage = process.memoryUsage();
|
||
|
|
spinner.succeed('Statistics gathered');
|
||
|
|
if (options.json) {
|
||
|
|
formatOutput(stats, options);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
console.log(chalk.cyan('\n📊 Database Statistics\n'));
|
||
|
|
// Core stats table
|
||
|
|
const coreTable = new Table({
|
||
|
|
head: [chalk.cyan('Metric'), chalk.cyan('Value')],
|
||
|
|
style: { head: [], border: [] }
|
||
|
|
});
|
||
|
|
coreTable.push(['Total Items', chalk.green(stats.nounCount + stats.verbCount + stats.metadataCount || 0)], ['Nouns', chalk.green(stats.nounCount || 0)], ['Verbs (Relationships)', chalk.green(stats.verbCount || 0)], ['Metadata Records', chalk.green(stats.metadataCount || 0)]);
|
||
|
|
console.log(coreTable.toString());
|
||
|
|
// Service breakdown if available
|
||
|
|
if (options.byService && stats.serviceBreakdown) {
|
||
|
|
console.log(chalk.cyan('\n🔧 Service Breakdown\n'));
|
||
|
|
const serviceTable = new Table({
|
||
|
|
head: [chalk.cyan('Service'), chalk.cyan('Nouns'), chalk.cyan('Verbs'), chalk.cyan('Metadata')],
|
||
|
|
style: { head: [], border: [] }
|
||
|
|
});
|
||
|
|
Object.entries(stats.serviceBreakdown).forEach(([service, serviceStats]) => {
|
||
|
|
serviceTable.push([
|
||
|
|
service,
|
||
|
|
serviceStats.nounCount || 0,
|
||
|
|
serviceStats.verbCount || 0,
|
||
|
|
serviceStats.metadataCount || 0
|
||
|
|
]);
|
||
|
|
});
|
||
|
|
console.log(serviceTable.toString());
|
||
|
|
}
|
||
|
|
// Storage info
|
||
|
|
if (stats.storage) {
|
||
|
|
console.log(chalk.cyan('\n💾 Storage\n'));
|
||
|
|
const storageTable = new Table({
|
||
|
|
head: [chalk.cyan('Property'), chalk.cyan('Value')],
|
||
|
|
style: { head: [], border: [] }
|
||
|
|
});
|
||
|
|
storageTable.push(['Type', stats.storage.type || 'Unknown'], ['Size', stats.storage.size ? formatBytes(stats.storage.size) : 'N/A'], ['Location', stats.storage.location || 'N/A']);
|
||
|
|
console.log(storageTable.toString());
|
||
|
|
}
|
||
|
|
// Performance metrics
|
||
|
|
if (stats.performance && options.detailed) {
|
||
|
|
console.log(chalk.cyan('\n⚡ Performance\n'));
|
||
|
|
const perfTable = new Table({
|
||
|
|
head: [chalk.cyan('Metric'), chalk.cyan('Value')],
|
||
|
|
style: { head: [], border: [] }
|
||
|
|
});
|
||
|
|
if (stats.performance.avgQueryTime) {
|
||
|
|
perfTable.push(['Avg Query Time', `${stats.performance.avgQueryTime.toFixed(2)} ms`]);
|
||
|
|
}
|
||
|
|
if (stats.performance.totalQueries) {
|
||
|
|
perfTable.push(['Total Queries', stats.performance.totalQueries]);
|
||
|
|
}
|
||
|
|
if (stats.performance.cacheHitRate) {
|
||
|
|
perfTable.push(['Cache Hit Rate', `${(stats.performance.cacheHitRate * 100).toFixed(1)}%`]);
|
||
|
|
}
|
||
|
|
console.log(perfTable.toString());
|
||
|
|
}
|
||
|
|
// Memory usage
|
||
|
|
console.log(chalk.cyan('\n🧠 Memory Usage\n'));
|
||
|
|
const memTable = new Table({
|
||
|
|
head: [chalk.cyan('Type'), chalk.cyan('Size')],
|
||
|
|
style: { head: [], border: [] }
|
||
|
|
});
|
||
|
|
memTable.push(['Heap Used', formatBytes(memUsage.heapUsed)], ['Heap Total', formatBytes(memUsage.heapTotal)], ['RSS', formatBytes(memUsage.rss)], ['External', formatBytes(memUsage.external)]);
|
||
|
|
console.log(memTable.toString());
|
||
|
|
// Index info
|
||
|
|
if (stats.index && options.detailed) {
|
||
|
|
console.log(chalk.cyan('\n🎯 Vector Index\n'));
|
||
|
|
const indexTable = new Table({
|
||
|
|
head: [chalk.cyan('Property'), chalk.cyan('Value')],
|
||
|
|
style: { head: [], border: [] }
|
||
|
|
});
|
||
|
|
indexTable.push(['Dimensions', stats.index.dimensions || 'N/A'], ['Indexed Vectors', stats.index.vectorCount || 0], ['Index Size', stats.index.indexSize ? formatBytes(stats.index.indexSize) : 'N/A']);
|
||
|
|
console.log(indexTable.toString());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
catch (error) {
|
||
|
|
spinner.fail('Failed to gather statistics');
|
||
|
|
console.error(chalk.red(error.message));
|
||
|
|
process.exit(1);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
/**
|
||
|
|
* Clean and optimize database
|
||
|
|
*/
|
||
|
|
async clean(options) {
|
||
|
|
const spinner = ora('Cleaning database...').start();
|
||
|
|
try {
|
||
|
|
const brain = await getBrainy();
|
||
|
|
const tasks = [];
|
||
|
|
if (options.removeOrphans) {
|
||
|
|
spinner.text = 'Removing orphaned items...';
|
||
|
|
tasks.push('Removed orphaned items');
|
||
|
|
// Implementation would go here
|
||
|
|
await new Promise(resolve => setTimeout(resolve, 500)); // Simulate work
|
||
|
|
}
|
||
|
|
if (options.rebuildIndex) {
|
||
|
|
spinner.text = 'Rebuilding search index...';
|
||
|
|
tasks.push('Rebuilt search index');
|
||
|
|
// Implementation would go here
|
||
|
|
await new Promise(resolve => setTimeout(resolve, 1000)); // Simulate work
|
||
|
|
}
|
||
|
|
if (tasks.length === 0) {
|
||
|
|
spinner.text = 'Running general cleanup...';
|
||
|
|
tasks.push('General cleanup completed');
|
||
|
|
// Run general cleanup tasks
|
||
|
|
await new Promise(resolve => setTimeout(resolve, 500)); // Simulate work
|
||
|
|
}
|
||
|
|
spinner.succeed('Database cleaned');
|
||
|
|
if (!options.json) {
|
||
|
|
console.log(chalk.green('\n✓ Cleanup completed:'));
|
||
|
|
tasks.forEach(task => {
|
||
|
|
console.log(chalk.dim(` • ${task}`));
|
||
|
|
});
|
||
|
|
// Get new stats
|
||
|
|
const stats = await brain.getStatistics();
|
||
|
|
console.log(chalk.cyan('\nDatabase Status:'));
|
||
|
|
console.log(` Total items: ${stats.nounCount + stats.verbCount}`);
|
||
|
|
console.log(` Index status: ${chalk.green('Healthy')}`);
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
formatOutput({ tasks, success: true }, options);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
catch (error) {
|
||
|
|
spinner.fail('Cleanup failed');
|
||
|
|
console.error(chalk.red(error.message));
|
||
|
|
process.exit(1);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
/**
|
||
|
|
* Run performance benchmarks
|
||
|
|
*/
|
||
|
|
async benchmark(options) {
|
||
|
|
const operations = options.operations || 'all';
|
||
|
|
const iterations = parseInt(options.iterations || '100');
|
||
|
|
console.log(chalk.cyan(`\n🚀 Running Benchmarks (${iterations} iterations)\n`));
|
||
|
|
const results = {
|
||
|
|
operations: {},
|
||
|
|
summary: {}
|
||
|
|
};
|
||
|
|
try {
|
||
|
|
const brain = await getBrainy();
|
||
|
|
// Benchmark different operations
|
||
|
|
const benchmarks = [
|
||
|
|
{ name: 'add', enabled: operations === 'all' || operations.includes('add') },
|
||
|
|
{ name: 'search', enabled: operations === 'all' || operations.includes('search') },
|
||
|
|
{ name: 'similarity', enabled: operations === 'all' || operations.includes('similarity') },
|
||
|
|
{ name: 'cluster', enabled: operations === 'all' || operations.includes('cluster') }
|
||
|
|
];
|
||
|
|
for (const bench of benchmarks) {
|
||
|
|
if (!bench.enabled)
|
||
|
|
continue;
|
||
|
|
const spinner = ora(`Benchmarking ${bench.name}...`).start();
|
||
|
|
const times = [];
|
||
|
|
for (let i = 0; i < iterations; i++) {
|
||
|
|
const start = Date.now();
|
||
|
|
switch (bench.name) {
|
||
|
|
case 'add':
|
||
|
|
await brain.add(`Test item ${i}`, { benchmark: true });
|
||
|
|
break;
|
||
|
|
case 'search':
|
||
|
|
await brain.search('test', 10);
|
||
|
|
break;
|
||
|
|
case 'similarity':
|
||
|
|
const neural = brain.neural;
|
||
|
|
await neural.similar('test1', 'test2');
|
||
|
|
break;
|
||
|
|
case 'cluster':
|
||
|
|
const neuralApi = brain.neural;
|
||
|
|
await neuralApi.clusters();
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
times.push(Date.now() - start);
|
||
|
|
}
|
||
|
|
// Calculate statistics
|
||
|
|
const avg = times.reduce((a, b) => a + b, 0) / times.length;
|
||
|
|
const min = Math.min(...times);
|
||
|
|
const max = Math.max(...times);
|
||
|
|
const median = times.sort((a, b) => a - b)[Math.floor(times.length / 2)];
|
||
|
|
results.operations[bench.name] = {
|
||
|
|
avg: avg.toFixed(2),
|
||
|
|
min,
|
||
|
|
max,
|
||
|
|
median,
|
||
|
|
ops: (1000 / avg).toFixed(2)
|
||
|
|
};
|
||
|
|
spinner.succeed(`${bench.name}: ${avg.toFixed(2)}ms avg (${(1000 / avg).toFixed(2)} ops/sec)`);
|
||
|
|
}
|
||
|
|
// Calculate summary
|
||
|
|
const totalOps = Object.values(results.operations).reduce((sum, op) => sum + parseFloat(op.ops), 0);
|
||
|
|
results.summary = {
|
||
|
|
totalOperations: Object.keys(results.operations).length,
|
||
|
|
averageOpsPerSec: (totalOps / Object.keys(results.operations).length).toFixed(2)
|
||
|
|
};
|
||
|
|
if (!options.json) {
|
||
|
|
// Display results table
|
||
|
|
console.log(chalk.cyan('\n📊 Benchmark Results\n'));
|
||
|
|
const table = new Table({
|
||
|
|
head: [
|
||
|
|
chalk.cyan('Operation'),
|
||
|
|
chalk.cyan('Avg (ms)'),
|
||
|
|
chalk.cyan('Min (ms)'),
|
||
|
|
chalk.cyan('Max (ms)'),
|
||
|
|
chalk.cyan('Median (ms)'),
|
||
|
|
chalk.cyan('Ops/sec')
|
||
|
|
],
|
||
|
|
style: { head: [], border: [] }
|
||
|
|
});
|
||
|
|
Object.entries(results.operations).forEach(([op, stats]) => {
|
||
|
|
table.push([
|
||
|
|
op,
|
||
|
|
stats.avg,
|
||
|
|
stats.min,
|
||
|
|
stats.max,
|
||
|
|
stats.median,
|
||
|
|
chalk.green(stats.ops)
|
||
|
|
]);
|
||
|
|
});
|
||
|
|
console.log(table.toString());
|
||
|
|
console.log(chalk.cyan('\n📈 Summary'));
|
||
|
|
console.log(` Operations tested: ${results.summary.totalOperations}`);
|
||
|
|
console.log(` Average throughput: ${chalk.green(results.summary.averageOpsPerSec)} ops/sec`);
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
formatOutput(results, options);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
catch (error) {
|
||
|
|
console.error(chalk.red('Benchmark failed:'), error.message);
|
||
|
|
process.exit(1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
};
|
||
|
|
//# sourceMappingURL=utility.js.map
|