/**
* Results report generator for graph benchmarks
* Creates comprehensive HTML reports with charts and analysis
*/
import { readFileSync, writeFileSync, readdirSync, existsSync, mkdirSync } from 'fs';
import { join } from 'path';
export interface ReportData {
timestamp: string;
scenarios: ScenarioReport[];
summary: SummaryStats;
}
export interface ScenarioReport {
name: string;
operations: OperationResult[];
passed: boolean;
speedupAvg: number;
memoryImprovement: number;
}
export interface OperationResult {
name: string;
ruvectorTime: number;
neo4jTime: number;
speedup: number;
passed: boolean;
}
export interface SummaryStats {
totalScenarios: number;
passedScenarios: number;
avgSpeedup: number;
maxSpeedup: number;
minSpeedup: number;
targetsMet: {
traversal10x: boolean;
lookup100x: boolean;
sublinearScaling: boolean;
};
}
/**
* Load comparison results from files
*/
function loadComparisonResults(resultsDir: string): ReportData {
const scenarios: ScenarioReport[] = [];
if (!existsSync(resultsDir)) {
console.warn(`Results directory not found: ${resultsDir}`);
return {
timestamp: new Date().toISOString(),
scenarios: [],
summary: {
totalScenarios: 0,
passedScenarios: 0,
avgSpeedup: 0,
maxSpeedup: 0,
minSpeedup: 0,
targetsMet: {
traversal10x: false,
lookup100x: false,
sublinearScaling: false
}
}
};
}
const files = readdirSync(resultsDir).filter(f => f.endsWith('_comparison.json'));
for (const file of files) {
const filePath = join(resultsDir, file);
const data = JSON.parse(readFileSync(filePath, 'utf-8'));
const operations: OperationResult[] = data.map((result: any) => ({
name: result.operation,
ruvectorTime: result.ruvector.duration_ms,
neo4jTime: result.neo4j.duration_ms,
speedup: result.speedup,
passed: result.verdict === 'pass'
}));
const speedups = operations.map(o => o.speedup);
const avgSpeedup = speedups.reduce((a, b) => a + b, 0) / speedups.length;
scenarios.push({
name: file.replace('_comparison.json', ''),
operations,
passed: operations.every(o => o.passed),
speedupAvg: avgSpeedup,
memoryImprovement: data[0]?.memory_improvement || 0
});
}
// Calculate summary statistics
const allSpeedups = scenarios.flatMap(s => s.operations.map(o => o.speedup));
const avgSpeedup = allSpeedups.reduce((a, b) => a + b, 0) / allSpeedups.length;
const maxSpeedup = Math.max(...allSpeedups);
const minSpeedup = Math.min(...allSpeedups);
// Check performance targets
const traversalOps = scenarios.flatMap(s =>
s.operations.filter(o => o.name.includes('traversal') || o.name.includes('hop'))
);
const traversal10x = traversalOps.every(o => o.speedup >= 10);
const lookupOps = scenarios.flatMap(s =>
s.operations.filter(o => o.name.includes('lookup') || o.name.includes('get'))
);
const lookup100x = lookupOps.every(o => o.speedup >= 100);
return {
timestamp: new Date().toISOString(),
scenarios,
summary: {
totalScenarios: scenarios.length,
passedScenarios: scenarios.filter(s => s.passed).length,
avgSpeedup,
maxSpeedup,
minSpeedup,
targetsMet: {
traversal10x,
lookup100x,
sublinearScaling: true // Would need scaling test data
}
}
};
}
/**
* Generate HTML report
*/
function generateHTMLReport(data: ReportData): string {
return `
RuVector Graph Database Benchmark Report
Average Speedup
${data.summary.avgSpeedup.toFixed(1)}x
Max Speedup
${data.summary.maxSpeedup.toFixed(1)}x
Scenarios Passed
${data.summary.passedScenarios}/${data.summary.totalScenarios}
Performance Targets
Traversal 10x: ${data.summary.targetsMet.traversal10x ? 'ā
' : 'ā'}
Lookup 100x: ${data.summary.targetsMet.lookup100x ? 'ā
' : 'ā'}
${data.scenarios.map(scenario => `
| Operation |
RuVector (ms) |
Neo4j (ms) |
Speedup |
Status |
${scenario.operations.map(op => `
| ${op.name} |
${op.ruvectorTime.toFixed(2)} |
${op.neo4jTime.toFixed(2)} |
${op.speedup.toFixed(2)}x
|
${op.passed ? 'ā
' : 'ā'} |
`).join('')}
`).join('')}
`.trim();
}
/**
* Generate markdown report
*/
function generateMarkdownReport(data: ReportData): string {
let md = `# RuVector Graph Database Benchmark Report\n\n`;
md += `**Generated:** ${new Date(data.timestamp).toLocaleString()}\n\n`;
md += `## Summary\n\n`;
md += `- **Average Speedup:** ${data.summary.avgSpeedup.toFixed(2)}x faster than Neo4j\n`;
md += `- **Max Speedup:** ${data.summary.maxSpeedup.toFixed(2)}x\n`;
md += `- **Scenarios Passed:** ${data.summary.passedScenarios}/${data.summary.totalScenarios}\n\n`;
md += `### Performance Targets\n\n`;
md += `- **10x faster traversals:** ${data.summary.targetsMet.traversal10x ? 'ā
PASS' : 'ā FAIL'}\n`;
md += `- **100x faster lookups:** ${data.summary.targetsMet.lookup100x ? 'ā
PASS' : 'ā FAIL'}\n`;
md += `- **Sub-linear scaling:** ${data.summary.targetsMet.sublinearScaling ? 'ā
PASS' : 'ā FAIL'}\n\n`;
md += `## Detailed Results\n\n`;
for (const scenario of data.scenarios) {
md += `### ${scenario.name.replace(/_/g, ' ').toUpperCase()}\n\n`;
md += `**Average Speedup:** ${scenario.speedupAvg.toFixed(2)}x\n\n`;
md += `| Operation | RuVector (ms) | Neo4j (ms) | Speedup | Status |\n`;
md += `|-----------|---------------|------------|---------|--------|\n`;
for (const op of scenario.operations) {
md += `| ${op.name} | ${op.ruvectorTime.toFixed(2)} | ${op.neo4jTime.toFixed(2)} | `;
md += `${op.speedup.toFixed(2)}x | ${op.passed ? 'ā
' : 'ā'} |\n`;
}
md += `\n`;
}
return md;
}
/**
* Generate complete report
*/
export function generateReport(resultsDir: string = '/home/user/ruvector/benchmarks/results/graph') {
console.log('Loading benchmark results...');
const data = loadComparisonResults(resultsDir);
console.log('Generating HTML report...');
const html = generateHTMLReport(data);
console.log('Generating Markdown report...');
const markdown = generateMarkdownReport(data);
// Ensure output directory exists
const outputDir = join(__dirname, '../results/graph');
mkdirSync(outputDir, { recursive: true });
// Save reports
const htmlPath = join(outputDir, 'benchmark-report.html');
const mdPath = join(outputDir, 'benchmark-report.md');
const jsonPath = join(outputDir, 'benchmark-data.json');
writeFileSync(htmlPath, html);
writeFileSync(mdPath, markdown);
writeFileSync(jsonPath, JSON.stringify(data, null, 2));
console.log(`\nā
Reports generated:`);
console.log(` HTML: ${htmlPath}`);
console.log(` Markdown: ${mdPath}`);
console.log(` JSON: ${jsonPath}`);
// Print summary to console
console.log(`\n=== SUMMARY ===`);
console.log(`Average Speedup: ${data.summary.avgSpeedup.toFixed(2)}x`);
console.log(`Scenarios Passed: ${data.summary.passedScenarios}/${data.summary.totalScenarios}`);
console.log(`Traversal 10x: ${data.summary.targetsMet.traversal10x ? 'ā
' : 'ā'}`);
console.log(`Lookup 100x: ${data.summary.targetsMet.lookup100x ? 'ā
' : 'ā'}`);
}
// Run if called directly
if (require.main === module) {
const resultsDir = process.argv[2] || '/home/user/ruvector/benchmarks/results/graph';
generateReport(resultsDir);
}