- Create benchmark test suites for critical operations: - Node loading performance - Database query performance - Search operations performance - Validation performance - MCP tool execution performance - Add GitHub Actions workflow for benchmark tracking: - Runs on push to main and PRs - Uses github-action-benchmark for historical tracking - Comments on PRs with performance results - Alerts on >10% performance regressions - Stores results in GitHub Pages - Create benchmark infrastructure: - Custom Vitest benchmark configuration - JSON reporter for CI results - Result formatter for github-action-benchmark - Performance threshold documentation - Add supporting utilities: - SQLiteStorageService for benchmark database setup - MCPEngine wrapper for testing MCP tools - Test factories for generating benchmark data - Enhanced NodeRepository with benchmark methods - Document benchmark system: - Comprehensive benchmark guide in docs/BENCHMARKS.md - Performance thresholds in .github/BENCHMARK_THRESHOLDS.md - README for benchmarks directory - Integration with existing test suite The benchmark system will help monitor performance over time and catch regressions before they reach production. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
143 lines
3.6 KiB
TypeScript
143 lines
3.6 KiB
TypeScript
import { bench, describe } from 'vitest';
|
|
import { NodeRepository } from '../../src/database/node-repository';
|
|
import { SQLiteStorageService } from '../../src/services/sqlite-storage-service';
|
|
import { NodeLoader } from '../../src/loaders/node-loader';
|
|
|
|
describe('Search Operations Performance', () => {
|
|
let repository: NodeRepository;
|
|
let storage: SQLiteStorageService;
|
|
|
|
beforeAll(async () => {
|
|
storage = new SQLiteStorageService(':memory:');
|
|
repository = new NodeRepository(storage);
|
|
const loader = new NodeLoader(repository);
|
|
|
|
// Load real nodes for realistic benchmarking
|
|
await loader.loadPackage('n8n-nodes-base');
|
|
});
|
|
|
|
afterAll(() => {
|
|
storage.close();
|
|
});
|
|
|
|
bench('searchNodes - single word', async () => {
|
|
await repository.searchNodes('http', 'OR', 20);
|
|
}, {
|
|
iterations: 100,
|
|
warmupIterations: 10,
|
|
warmupTime: 500,
|
|
time: 3000
|
|
});
|
|
|
|
bench('searchNodes - multiple words OR', async () => {
|
|
await repository.searchNodes('http request webhook', 'OR', 20);
|
|
}, {
|
|
iterations: 100,
|
|
warmupIterations: 10,
|
|
warmupTime: 500,
|
|
time: 3000
|
|
});
|
|
|
|
bench('searchNodes - multiple words AND', async () => {
|
|
await repository.searchNodes('http request', 'AND', 20);
|
|
}, {
|
|
iterations: 100,
|
|
warmupIterations: 10,
|
|
warmupTime: 500,
|
|
time: 3000
|
|
});
|
|
|
|
bench('searchNodes - fuzzy search', async () => {
|
|
await repository.searchNodes('htpp requst', 'FUZZY', 20);
|
|
}, {
|
|
iterations: 100,
|
|
warmupIterations: 10,
|
|
warmupTime: 500,
|
|
time: 3000
|
|
});
|
|
|
|
bench('searchNodes - exact phrase', async () => {
|
|
await repository.searchNodes('"HTTP Request"', 'OR', 20);
|
|
}, {
|
|
iterations: 100,
|
|
warmupIterations: 10,
|
|
warmupTime: 500,
|
|
time: 3000
|
|
});
|
|
|
|
bench('searchNodes - large result set', async () => {
|
|
await repository.searchNodes('data', 'OR', 100);
|
|
}, {
|
|
iterations: 50,
|
|
warmupIterations: 5,
|
|
warmupTime: 500,
|
|
time: 3000
|
|
});
|
|
|
|
bench('searchNodes - no results', async () => {
|
|
await repository.searchNodes('xyznonexistentquery123', 'OR', 20);
|
|
}, {
|
|
iterations: 200,
|
|
warmupIterations: 20,
|
|
warmupTime: 500,
|
|
time: 3000
|
|
});
|
|
|
|
bench('searchNodeProperties - common property', async () => {
|
|
const node = await repository.getNodeByType('n8n-nodes-base.httpRequest');
|
|
if (node) {
|
|
await repository.searchNodeProperties(node.type, 'url', 20);
|
|
}
|
|
}, {
|
|
iterations: 100,
|
|
warmupIterations: 10,
|
|
warmupTime: 500,
|
|
time: 3000
|
|
});
|
|
|
|
bench('searchNodeProperties - nested property', async () => {
|
|
const node = await repository.getNodeByType('n8n-nodes-base.httpRequest');
|
|
if (node) {
|
|
await repository.searchNodeProperties(node.type, 'authentication', 20);
|
|
}
|
|
}, {
|
|
iterations: 100,
|
|
warmupIterations: 10,
|
|
warmupTime: 500,
|
|
time: 3000
|
|
});
|
|
|
|
bench('getNodesByCategory - all categories', async () => {
|
|
const categories = ['trigger', 'transform', 'output', 'input'];
|
|
for (const category of categories) {
|
|
await repository.getNodesByCategory(category);
|
|
}
|
|
}, {
|
|
iterations: 50,
|
|
warmupIterations: 5,
|
|
warmupTime: 500,
|
|
time: 3000
|
|
});
|
|
|
|
bench('getNodesByPackage', async () => {
|
|
await repository.getNodesByPackage('n8n-nodes-base');
|
|
}, {
|
|
iterations: 50,
|
|
warmupIterations: 5,
|
|
warmupTime: 500,
|
|
time: 3000
|
|
});
|
|
|
|
bench('complex filter - AI tools in transform category', async () => {
|
|
const allNodes = await repository.getAllNodes();
|
|
const filtered = allNodes.filter(node =>
|
|
node.category === 'transform' &&
|
|
node.isAITool
|
|
);
|
|
}, {
|
|
iterations: 100,
|
|
warmupIterations: 10,
|
|
warmupTime: 500,
|
|
time: 3000
|
|
});
|
|
}); |