UNPKG

claude-flow

Version:

Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration

1,086 lines (954 loc) 36.6 kB
/** * Performance Optimizer MCP Tools * * 5 MCP tools for AI-powered performance optimization: * 1. perf/bottleneck-detect - Detect performance bottlenecks * 2. perf/memory-analyze - Analyze memory usage and leaks * 3. perf/query-optimize - Detect and optimize query patterns * 4. perf/bundle-optimize - Optimize JavaScript bundles * 5. perf/config-optimize - Optimize configuration parameters */ import type { MCPTool, MCPToolResult, ToolContext, BottleneckDetectOutput, Bottleneck, MemoryAnalyzeOutput, MemoryLeak, HeapObject, QueryOptimizeOutput, QueryPattern, IndexSuggestion, BundleOptimizeOutput, BundleOptimization, ConfigOptimizeOutput, ConfigParameter, TraceSpan, } from './types.js'; import { BottleneckDetectInputSchema, MemoryAnalyzeInputSchema, QueryOptimizeInputSchema, BundleOptimizeInputSchema, ConfigOptimizeInputSchema, successResult, errorResult, } from './types.js'; // ============================================================================ // Default Logger // ============================================================================ const defaultLogger = { debug: (msg: string, meta?: Record<string, unknown>) => console.debug(`[perf-optimizer] ${msg}`, meta), info: (msg: string, meta?: Record<string, unknown>) => console.info(`[perf-optimizer] ${msg}`, meta), warn: (msg: string, meta?: Record<string, unknown>) => console.warn(`[perf-optimizer] ${msg}`, meta), error: (msg: string, meta?: Record<string, unknown>) => console.error(`[perf-optimizer] ${msg}`, meta), }; // ============================================================================ // Tool 1: perf/bottleneck-detect // ============================================================================ async function bottleneckDetectHandler( input: Record<string, unknown>, context?: ToolContext ): Promise<MCPToolResult> { const logger = context?.logger ?? defaultLogger; const startTime = performance.now(); try { const validationResult = BottleneckDetectInputSchema.safeParse(input); if (!validationResult.success) { return errorResult(`Invalid input: ${validationResult.error.message}`); } const { traceData, analysisScope, threshold } = validationResult.data; logger.debug('Detecting bottlenecks', { spanCount: traceData.spans.length, scope: analysisScope }); // Parse spans const spans = traceData.spans as TraceSpan[]; // Use sparse bridge if available let criticalPath: string[] = []; if (context?.sparseBridge?.isReady()) { const encoded = await context.sparseBridge.encodeTraces(spans); criticalPath = await context.sparseBridge.analyzeCriticalPath(encoded); } // Analyze for bottlenecks const bottlenecks = analyzeBottlenecks(spans, analysisScope, threshold); // Calculate latency percentiles const durations = spans.map(s => s.duration).sort((a, b) => a - b); const p50 = durations[Math.floor(durations.length * 0.5)] ?? 0; const p95 = durations[Math.floor(durations.length * 0.95)] ?? 0; const p99 = durations[Math.floor(durations.length * 0.99)] ?? 0; // Calculate error rate const errorCount = spans.filter(s => s.status === 'error').length; const errorRate = errorCount / Math.max(1, spans.length); // Calculate overall score (0 = bad, 1 = good) const overallScore = calculatePerformanceScore(bottlenecks, p95, errorRate); const output: BottleneckDetectOutput = { bottlenecks, criticalPath: criticalPath.length > 0 ? criticalPath : extractCriticalPath(spans), overallScore, details: { spanCount: spans.length, analysisScope, p50Latency: p50, p95Latency: p95, p99Latency: p99, errorRate, interpretation: getBottleneckInterpretation(bottlenecks, overallScore), }, }; const duration = performance.now() - startTime; logger.info('Bottleneck detection completed', { bottlenecksFound: bottlenecks.length, overallScore: overallScore.toFixed(2), durationMs: duration.toFixed(2), }); return successResult(output); } catch (error) { logger.error('Bottleneck detection failed', { error: String(error) }); return errorResult(error instanceof Error ? error : new Error(String(error))); } } export const bottleneckDetectTool: MCPTool = { name: 'perf/bottleneck-detect', description: 'Detect performance bottlenecks using GNN-based dependency analysis. Analyzes distributed traces to identify slow operations, resource contention, and critical paths.', category: 'performance', version: '0.1.0', tags: ['performance', 'tracing', 'bottleneck', 'analysis'], cacheable: false, inputSchema: { type: 'object', properties: { traceData: { type: 'object', properties: { format: { type: 'string', enum: ['otlp', 'chrome_devtools', 'jaeger', 'zipkin'] }, spans: { type: 'array' }, metrics: { type: 'object' }, }, }, analysisScope: { type: 'array', items: { type: 'string' } }, threshold: { type: 'object', properties: { latencyP95: { type: 'number' }, throughput: { type: 'number' }, errorRate: { type: 'number' }, }, }, }, required: ['traceData'], }, handler: bottleneckDetectHandler, }; // ============================================================================ // Tool 2: perf/memory-analyze // ============================================================================ async function memoryAnalyzeHandler( input: Record<string, unknown>, context?: ToolContext ): Promise<MCPToolResult> { const logger = context?.logger ?? defaultLogger; const startTime = performance.now(); try { const validationResult = MemoryAnalyzeInputSchema.safeParse(input); if (!validationResult.success) { return errorResult(`Invalid input: ${validationResult.error.message}`); } const { heapSnapshot, timeline, analysis, compareBaseline: _compareBaseline } = validationResult.data; // compareBaseline can be used for differential analysis void _compareBaseline; logger.debug('Analyzing memory', { hasSnapshot: !!heapSnapshot, timelinePoints: timeline?.length }); // Analyze memory (mock implementation) const leaks = generateMockMemoryLeaks(analysis ?? ['leak_detection']); const hotspots = generateMockHotspots(); const gcPressure = calculateGcPressure(timeline as Array<{ timestamp: number; heapUsed: number }> | undefined); const output: MemoryAnalyzeOutput = { leaks, hotspots, gcPressure, details: { heapUsed: 256 * 1024 * 1024, heapTotal: 512 * 1024 * 1024, objectCount: 150000, analysisType: analysis ?? ['leak_detection'], interpretation: getMemoryInterpretation(leaks, gcPressure), }, }; const duration = performance.now() - startTime; logger.info('Memory analysis completed', { leaksFound: leaks.length, gcPressure: gcPressure.toFixed(2), durationMs: duration.toFixed(2), }); return successResult(output); } catch (error) { logger.error('Memory analysis failed', { error: String(error) }); return errorResult(error instanceof Error ? error : new Error(String(error))); } } export const memoryAnalyzeTool: MCPTool = { name: 'perf/memory-analyze', description: 'Analyze memory patterns and detect potential leaks. Identifies detached DOM nodes, closure leaks, event listener leaks, and unbounded caches.', category: 'performance', version: '0.1.0', tags: ['performance', 'memory', 'leak-detection', 'gc'], cacheable: false, inputSchema: { type: 'object', properties: { heapSnapshot: { type: 'string' }, timeline: { type: 'array' }, analysis: { type: 'array', items: { type: 'string' } }, compareBaseline: { type: 'string' }, }, }, handler: memoryAnalyzeHandler, }; // ============================================================================ // Tool 3: perf/query-optimize // ============================================================================ async function queryOptimizeHandler( input: Record<string, unknown>, context?: ToolContext ): Promise<MCPToolResult> { const logger = context?.logger ?? defaultLogger; const startTime = performance.now(); try { const validationResult = QueryOptimizeInputSchema.safeParse(input); if (!validationResult.success) { return errorResult(`Invalid input: ${validationResult.error.message}`); } const { queries, patterns: requestedPatterns, suggestIndexes } = validationResult.data; logger.debug('Optimizing queries', { queryCount: queries.length }); // Analyze query patterns const patterns = analyzeQueryPatterns(queries, requestedPatterns); // Generate index suggestions if requested const optimizations: IndexSuggestion[] = []; if (suggestIndexes) { for (const pattern of patterns.filter(p => p.type === 'missing_index')) { if (pattern.suggestedIndex) { optimizations.push(pattern.suggestedIndex); } } } // Count issues const slowQueries = queries.filter(q => q.duration > 100).length; const nPlusOneCount = patterns.filter(p => p.type === 'n_plus_1').length; const missingIndexCount = patterns.filter(p => p.type === 'missing_index').length; const output: QueryOptimizeOutput = { patterns, optimizations, totalQueries: queries.length, details: { slowQueries, nPlusOneCount, missingIndexCount, estimatedImprovement: calculateQueryImprovement(patterns), interpretation: getQueryInterpretation(patterns, slowQueries), }, }; const duration = performance.now() - startTime; logger.info('Query optimization completed', { patternsFound: patterns.length, indexSuggestions: optimizations.length, durationMs: duration.toFixed(2), }); return successResult(output); } catch (error) { logger.error('Query optimization failed', { error: String(error) }); return errorResult(error instanceof Error ? error : new Error(String(error))); } } export const queryOptimizeTool: MCPTool = { name: 'perf/query-optimize', description: 'Detect N+1 queries and suggest optimizations. Analyzes query patterns, identifies missing indexes, and provides actionable recommendations.', category: 'performance', version: '0.1.0', tags: ['performance', 'database', 'query', 'optimization'], cacheable: false, inputSchema: { type: 'object', properties: { queries: { type: 'array', items: { type: 'object', properties: { sql: { type: 'string' }, duration: { type: 'number' }, stackTrace: { type: 'string' }, resultSize: { type: 'number' }, }, }, }, patterns: { type: 'array', items: { type: 'string' } }, suggestIndexes: { type: 'boolean' }, }, required: ['queries'], }, handler: queryOptimizeHandler, }; // ============================================================================ // Tool 4: perf/bundle-optimize // ============================================================================ async function bundleOptimizeHandler( input: Record<string, unknown>, context?: ToolContext ): Promise<MCPToolResult> { const logger = context?.logger ?? defaultLogger; const startTime = performance.now(); try { const validationResult = BundleOptimizeInputSchema.safeParse(input); if (!validationResult.success) { return errorResult(`Invalid input: ${validationResult.error.message}`); } const { bundleStats, analysis, targets } = validationResult.data; logger.debug('Optimizing bundle', { statsPath: bundleStats, analysis }); // Analyze bundle (mock implementation) const optimizations = generateMockBundleOptimizations(analysis, targets); const totalSize = 1500 * 1024; // 1.5MB const potentialSavings = optimizations.reduce((s, o) => s + o.potentialSavings, 0); const output: BundleOptimizeOutput = { optimizations, totalSize, potentialSavings, details: { chunkCount: 12, moduleCount: 245, duplicateDeps: ['lodash', 'moment', 'axios'], largestModules: ['react-dom', 'chart.js', 'moment'], interpretation: getBundleInterpretation(totalSize, potentialSavings, targets?.maxSize), }, }; const duration = performance.now() - startTime; logger.info('Bundle optimization completed', { optimizationsFound: optimizations.length, potentialSavingsKb: (potentialSavings / 1024).toFixed(0), durationMs: duration.toFixed(2), }); return successResult(output); } catch (error) { logger.error('Bundle optimization failed', { error: String(error) }); return errorResult(error instanceof Error ? error : new Error(String(error))); } } export const bundleOptimizeTool: MCPTool = { name: 'perf/bundle-optimize', description: 'Analyze bundle size and suggest optimizations. Identifies tree-shaking opportunities, code splitting candidates, and duplicate dependencies.', category: 'performance', version: '0.1.0', tags: ['performance', 'bundle', 'webpack', 'optimization'], cacheable: true, cacheTTL: 300000, inputSchema: { type: 'object', properties: { bundleStats: { type: 'string' }, analysis: { type: 'array', items: { type: 'string' } }, targets: { type: 'object', properties: { maxSize: { type: 'number' }, maxChunks: { type: 'number' }, }, }, }, required: ['bundleStats'], }, handler: bundleOptimizeHandler, }; // ============================================================================ // Tool 5: perf/config-optimize // ============================================================================ async function configOptimizeHandler( input: Record<string, unknown>, context?: ToolContext ): Promise<MCPToolResult> { const logger = context?.logger ?? defaultLogger; const startTime = performance.now(); try { const validationResult = ConfigOptimizeInputSchema.safeParse(input); if (!validationResult.success) { return errorResult(`Invalid input: ${validationResult.error.message}`); } const { workloadProfile, configSpace, objective } = validationResult.data; logger.debug('Optimizing configuration', { workloadType: workloadProfile.type, objective }); // Use FPGA bridge if available let recommendations: ConfigParameter[] = []; let predictedImprovement = { latency: 0, throughput: 0, cost: 0 }; if (context?.fpgaBridge?.isReady()) { const result = await context.fpgaBridge.optimizeConfig( workloadProfile as any, configSpace ); recommendations = result.parameters; predictedImprovement = result.predictedImprovement; } else { // Fallback to mock implementation const result = generateMockConfigOptimization(workloadProfile, configSpace, objective); recommendations = result.recommendations; predictedImprovement = result.predictedImprovement; } const warnings: string[] = []; for (const param of recommendations) { if (param.impact < 0.1) { warnings.push(`Parameter '${param.name}' has minimal impact`); } } const output: ConfigOptimizeOutput = { recommendations, objective, predictedImprovement, details: { parametersAnalyzed: Object.keys(configSpace).length, optimizationsFound: recommendations.filter(r => r.suggested !== r.current).length, confidence: recommendations.reduce((s, r) => s + r.confidence, 0) / Math.max(1, recommendations.length), warnings, interpretation: getConfigInterpretation(predictedImprovement, objective), }, }; const duration = performance.now() - startTime; logger.info('Configuration optimization completed', { recommendations: recommendations.length, durationMs: duration.toFixed(2), }); return successResult(output); } catch (error) { logger.error('Configuration optimization failed', { error: String(error) }); return errorResult(error instanceof Error ? error : new Error(String(error))); } } export const configOptimizeTool: MCPTool = { name: 'perf/config-optimize', description: 'Suggest optimal configurations using SONA learning. Analyzes workload profiles and recommends configuration parameters for improved performance.', category: 'performance', version: '0.1.0', tags: ['performance', 'configuration', 'optimization', 'tuning'], cacheable: false, inputSchema: { type: 'object', properties: { workloadProfile: { type: 'object', properties: { type: { type: 'string', enum: ['web', 'api', 'batch', 'stream', 'hybrid'] }, metrics: { type: 'object' }, constraints: { type: 'object' }, }, }, configSpace: { type: 'object' }, objective: { type: 'string', enum: ['latency', 'throughput', 'cost', 'balanced'] }, }, required: ['workloadProfile', 'configSpace', 'objective'], }, handler: configOptimizeHandler, }; // ============================================================================ // Export All Tools // ============================================================================ export const perfOptimizerTools: MCPTool[] = [ bottleneckDetectTool, memoryAnalyzeTool, queryOptimizeTool, bundleOptimizeTool, configOptimizeTool, ]; // ============================================================================ // Helper Functions // ============================================================================ function analyzeBottlenecks( spans: TraceSpan[], scope: string[], threshold?: { latencyP95?: number; throughput?: number; errorRate?: number } ): Bottleneck[] { const bottlenecks: Bottleneck[] = []; const operationStats = new Map<string, { count: number; totalDuration: number; errors: number }>(); // Aggregate stats by operation for (const span of spans) { const key = `${span.serviceName}:${span.operationName}`; const stats = operationStats.get(key) ?? { count: 0, totalDuration: 0, errors: 0 }; stats.count++; stats.totalDuration += span.duration; if (span.status === 'error') stats.errors++; operationStats.set(key, stats); } // Find bottlenecks let idx = 0; for (const [operation, stats] of operationStats) { const avgDuration = stats.totalDuration / stats.count; const errorRate = stats.errors / stats.count; const shouldInclude = scope.includes('all') || scope.some(s => operation.toLowerCase().includes(s) || s === 'all' ); if (!shouldInclude) continue; // Check thresholds const latencyThreshold = threshold?.latencyP95 ?? 100; const errorThreshold = threshold?.errorRate ?? 0.01; if (avgDuration > latencyThreshold || errorRate > errorThreshold) { const severity = avgDuration > latencyThreshold * 5 || errorRate > 0.1 ? 'critical' : avgDuration > latencyThreshold * 2 || errorRate > 0.05 ? 'high' : avgDuration > latencyThreshold || errorRate > errorThreshold ? 'medium' : 'low'; bottlenecks.push({ id: `bn-${idx++}`, type: determineBottleneckType(operation, avgDuration), severity, location: operation, description: `${operation} has avg latency ${avgDuration.toFixed(0)}ms with ${(errorRate * 100).toFixed(1)}% error rate`, impact: { latencyMs: avgDuration, throughput: stats.count, errorRate, }, suggestedFix: getSuggestedFix(operation, avgDuration, errorRate), relatedSpans: spans.filter(s => `${s.serviceName}:${s.operationName}` === operation).slice(0, 5).map(s => s.spanId), }); } } return bottlenecks.sort((a, b) => b.impact.latencyMs - a.impact.latencyMs); } function determineBottleneckType(operation: string, duration: number): Bottleneck['type'] { const opLower = operation.toLowerCase(); if (opLower.includes('db') || opLower.includes('sql') || opLower.includes('query')) return 'database'; if (opLower.includes('http') || opLower.includes('fetch') || opLower.includes('api')) return 'network'; if (opLower.includes('render') || opLower.includes('paint')) return 'render'; if (opLower.includes('io') || opLower.includes('file') || opLower.includes('disk')) return 'io'; if (opLower.includes('gc') || opLower.includes('garbage')) return 'gc_pressure'; if (opLower.includes('lock') || opLower.includes('mutex')) return 'lock_contention'; if (duration > 500) return 'cpu'; return 'cpu'; } function getSuggestedFix(operation: string, latency: number, errorRate: number): string { const opLower = operation.toLowerCase(); if (opLower.includes('db') || opLower.includes('query')) { return 'Add database indexes, optimize query, or implement caching'; } if (opLower.includes('http') || opLower.includes('api')) { return 'Implement connection pooling, add caching, or reduce payload size'; } if (errorRate > 0.05) { return 'Investigate error patterns, add retry logic with backoff'; } if (latency > 1000) { return 'Consider async processing, add timeout, or optimize algorithm'; } return 'Profile operation for optimization opportunities'; } function extractCriticalPath(spans: TraceSpan[]): string[] { // Build span tree const spanMap = new Map<string, TraceSpan>(); const children = new Map<string, TraceSpan[]>(); for (const span of spans) { spanMap.set(span.spanId, span); if (span.parentSpanId) { const siblings = children.get(span.parentSpanId) ?? []; siblings.push(span); children.set(span.parentSpanId, siblings); } } // Find root spans const roots = spans.filter(s => !s.parentSpanId); if (roots.length === 0) return []; // Find longest path const path: string[] = []; let current: TraceSpan | undefined = roots.reduce((a, b) => a.duration > b.duration ? a : b); while (current) { path.push(`${current.serviceName}:${current.operationName}`); const childSpans = children.get(current.spanId); if (childSpans && childSpans.length > 0) { current = childSpans.reduce((a, b) => a.duration > b.duration ? a : b); } else { current = undefined; } } return path; } function calculatePerformanceScore(bottlenecks: Bottleneck[], p95: number, errorRate: number): number { let score = 1; // Penalize for bottlenecks for (const bn of bottlenecks) { switch (bn.severity) { case 'critical': score -= 0.3; break; case 'high': score -= 0.2; break; case 'medium': score -= 0.1; break; case 'low': score -= 0.05; break; } } // Penalize for high latency if (p95 > 1000) score -= 0.2; else if (p95 > 500) score -= 0.1; else if (p95 > 200) score -= 0.05; // Penalize for errors score -= errorRate * 2; return Math.max(0, Math.min(1, score)); } function getBottleneckInterpretation(bottlenecks: Bottleneck[], score: number): string { const critical = bottlenecks.filter(b => b.severity === 'critical').length; const high = bottlenecks.filter(b => b.severity === 'high').length; if (score >= 0.9) { return 'Excellent performance with no significant bottlenecks'; } if (score >= 0.7) { return `Good performance with ${bottlenecks.length} minor issues to address`; } if (score >= 0.5) { return `Moderate performance. ${high} high-severity bottlenecks need attention`; } return `Poor performance. ${critical} critical bottlenecks require immediate attention`; } function generateMockMemoryLeaks(analysisTypes: string[]): MemoryLeak[] { const leaks: MemoryLeak[] = []; if (analysisTypes.includes('leak_detection')) { leaks.push({ id: 'leak-1', type: 'event_listener', severity: 'high', object: 'HTMLDivElement', retainedSize: 5 * 1024 * 1024, growthRate: 100 * 1024, retainerPath: ['window', 'eventListeners', 'click', 'handler'], suggestedFix: 'Remove event listener in component cleanup', }); } if (analysisTypes.includes('allocation_hotspots')) { leaks.push({ id: 'leak-2', type: 'cache_unbounded', severity: 'medium', object: 'CacheMap', retainedSize: 10 * 1024 * 1024, growthRate: 50 * 1024, retainerPath: ['global', 'cache', 'entries'], suggestedFix: 'Implement LRU eviction policy for cache', }); } return leaks; } function generateMockHotspots(): HeapObject[] { return [ { name: 'strings', type: 'String', size: 50 * 1024 * 1024, count: 500000, shallowSize: 50 * 1024 * 1024, retainedSize: 50 * 1024 * 1024, }, { name: 'arrays', type: 'Array', size: 30 * 1024 * 1024, count: 100000, shallowSize: 10 * 1024 * 1024, retainedSize: 30 * 1024 * 1024, }, ]; } function calculateGcPressure(timeline: Array<{ timestamp: number; heapUsed: number }> | undefined): number { if (!timeline || timeline.length < 2) return 0.15; let gcEvents = 0; for (let i = 1; i < timeline.length; i++) { if (timeline[i].heapUsed < timeline[i - 1].heapUsed * 0.8) { gcEvents++; } } return Math.min(1, gcEvents / timeline.length); } function getMemoryInterpretation(leaks: MemoryLeak[], gcPressure: number): string { const critical = leaks.filter(l => l.severity === 'critical').length; if (leaks.length === 0 && gcPressure < 0.2) { return 'Healthy memory usage with no detected leaks'; } if (critical > 0) { return `Critical memory issues detected. ${critical} leak(s) require immediate attention`; } if (gcPressure > 0.5) { return 'High GC pressure detected. Consider reducing allocations'; } return `${leaks.length} potential memory issues detected. Review and address`; } function analyzeQueryPatterns( queries: Array<{ sql: string; duration: number; stackTrace?: string; resultSize?: number }>, requestedPatterns?: string[] ): QueryPattern[] { const patterns: QueryPattern[] = []; const queryGroups = new Map<string, typeof queries>(); // Group similar queries for (const query of queries) { const normalized = normalizeQuery(query.sql); const group = queryGroups.get(normalized) ?? []; group.push(query); queryGroups.set(normalized, group); } let idx = 0; for (const [normalized, group] of queryGroups) { // Detect N+1 if (group.length > 10 && normalized.toLowerCase().includes('where')) { if (!requestedPatterns || requestedPatterns.includes('n_plus_1')) { patterns.push({ id: `qp-${idx++}`, type: 'n_plus_1', severity: group.length > 50 ? 'critical' : group.length > 20 ? 'high' : 'medium', queries: group.slice(0, 5).map(q => q.sql), count: group.length, totalDuration: group.reduce((s, q) => s + q.duration, 0), suggestedFix: 'Batch queries or use eager loading', }); } } // Detect slow queries (missing index) const avgDuration = group.reduce((s, q) => s + q.duration, 0) / group.length; if (avgDuration > 100 && normalized.toLowerCase().includes('where')) { if (!requestedPatterns || requestedPatterns.includes('missing_index')) { const columns = extractWhereColumns(normalized); patterns.push({ id: `qp-${idx++}`, type: 'missing_index', severity: avgDuration > 500 ? 'critical' : avgDuration > 200 ? 'high' : 'medium', queries: group.slice(0, 3).map(q => q.sql), count: group.length, totalDuration: group.reduce((s, q) => s + q.duration, 0), suggestedFix: `Add index on columns: ${columns.join(', ')}`, suggestedIndex: columns.length > 0 ? { table: extractTableName(normalized), columns, type: 'btree', estimatedImprovement: 0.7, createStatement: `CREATE INDEX idx_${extractTableName(normalized)}_${columns.join('_')} ON ${extractTableName(normalized)} (${columns.join(', ')})`, } : undefined, }); } } // Detect full scans const hasLargeResults = group.some(q => (q.resultSize ?? 0) > 1000); if (hasLargeResults && !normalized.toLowerCase().includes('limit')) { if (!requestedPatterns || requestedPatterns.includes('full_scan')) { patterns.push({ id: `qp-${idx++}`, type: 'full_scan', severity: 'medium', queries: group.slice(0, 3).map(q => q.sql), count: group.length, totalDuration: group.reduce((s, q) => s + q.duration, 0), suggestedFix: 'Add LIMIT clause or filter conditions', }); } } } return patterns; } function normalizeQuery(sql: string): string { return sql .replace(/\s+/g, ' ') .replace(/= \d+/g, '= ?') .replace(/= '[^']*'/g, "= '?'") .replace(/IN \([^)]+\)/gi, 'IN (?)') .trim() .toLowerCase(); } function extractWhereColumns(sql: string): string[] { const columns: string[] = []; const whereMatch = sql.match(/where\s+(.+?)(?:order|group|limit|$)/i); if (whereMatch) { const conditions = whereMatch[1].split(/\s+and\s+/i); for (const condition of conditions) { const colMatch = condition.match(/(\w+)\s*[=<>]/); if (colMatch) { columns.push(colMatch[1]); } } } return columns; } function extractTableName(sql: string): string { const match = sql.match(/from\s+(\w+)/i); return match ? match[1] : 'unknown'; } function calculateQueryImprovement(patterns: QueryPattern[]): number { let improvement = 0; for (const pattern of patterns) { switch (pattern.type) { case 'n_plus_1': improvement += 50; break; case 'missing_index': improvement += 40; break; case 'full_scan': improvement += 20; break; default: improvement += 10; } } return Math.min(90, improvement); } function getQueryInterpretation(patterns: QueryPattern[], slowQueries: number): string { const nPlus1 = patterns.filter(p => p.type === 'n_plus_1').length; if (patterns.length === 0) { return 'No problematic query patterns detected'; } if (nPlus1 > 0) { return `${nPlus1} N+1 query pattern(s) detected. This is a common performance killer - prioritize fixing`; } if (slowQueries > 10) { return `${slowQueries} slow queries found. Consider adding indexes or optimizing`; } return `${patterns.length} query optimization opportunities identified`; } function generateMockBundleOptimizations( analysis?: string[], _targets?: { maxSize?: number; maxChunks?: number } ): BundleOptimization[] { // targets can be used for target-aware optimization in future void _targets; const optimizations: BundleOptimization[] = []; const analysisTypes = analysis ?? ['tree_shaking', 'duplicate_deps', 'large_modules']; if (analysisTypes.includes('duplicate_deps')) { optimizations.push({ id: 'bo-1', type: 'duplicate_deps', severity: 'high', target: 'lodash', currentSize: 70 * 1024, potentialSavings: 50 * 1024, description: 'Multiple versions of lodash detected', suggestedFix: 'Use npm dedupe or specify a single version in package.json resolutions', }); } if (analysisTypes.includes('large_modules')) { optimizations.push({ id: 'bo-2', type: 'large_modules', severity: 'medium', target: 'moment', currentSize: 290 * 1024, potentialSavings: 250 * 1024, description: 'moment.js includes all locales by default', suggestedFix: 'Switch to date-fns or dayjs, or exclude unused locales', }); } if (analysisTypes.includes('code_splitting')) { optimizations.push({ id: 'bo-3', type: 'code_splitting', severity: 'medium', target: 'chart.js', currentSize: 200 * 1024, potentialSavings: 150 * 1024, description: 'Large module loaded synchronously', suggestedFix: 'Use dynamic import() for lazy loading', }); } if (analysisTypes.includes('tree_shaking')) { optimizations.push({ id: 'bo-4', type: 'tree_shaking', severity: 'low', target: 'src/utils', currentSize: 50 * 1024, potentialSavings: 30 * 1024, description: 'Unused exports detected', suggestedFix: 'Enable sideEffects: false in package.json or remove unused code', }); } return optimizations; } function getBundleInterpretation(totalSize: number, savings: number, maxSize?: number): string { const sizeKb = totalSize / 1024; const savingsKb = savings / 1024; if (maxSize && sizeKb > maxSize) { return `Bundle size ${sizeKb.toFixed(0)}KB exceeds target ${maxSize}KB. ${savingsKb.toFixed(0)}KB can be saved`; } if (savings > 0) { return `Bundle size ${sizeKb.toFixed(0)}KB with ${savingsKb.toFixed(0)}KB optimization potential (${(savings / totalSize * 100).toFixed(0)}% reduction)`; } return `Bundle size ${sizeKb.toFixed(0)}KB is well optimized`; } function generateMockConfigOptimization( workload: Record<string, unknown>, configSpace: Record<string, unknown>, objective: string ): { recommendations: ConfigParameter[]; predictedImprovement: { latency: number; throughput: number; cost: number } } { const recommendations: ConfigParameter[] = []; // Extract workload type for future workload-specific optimization const _workloadType = (workload as { type?: string }).type ?? 'web'; void _workloadType; for (const [name, spec] of Object.entries(configSpace)) { const paramSpec = spec as { type: string; range?: unknown[]; current: unknown }; let suggested = paramSpec.current; let impact = 0.2; if (paramSpec.type === 'number' && paramSpec.range) { const [min, max] = paramSpec.range as number[]; const current = paramSpec.current as number; // Optimize based on objective if (objective === 'latency') { suggested = Math.min(max, current * 1.5); } else if (objective === 'throughput') { suggested = max * 0.8; } else if (objective === 'cost') { suggested = (min + max) / 2; } else { suggested = (current + max) / 2; } impact = Math.abs((suggested as number) - current) / (max - min); } recommendations.push({ name, type: paramSpec.type as 'number' | 'boolean' | 'string' | 'enum', current: paramSpec.current, suggested, range: paramSpec.type === 'number' ? paramSpec.range as [number, number] : undefined, impact: Math.min(1, impact), confidence: 0.7 + Math.random() * 0.2, }); } return { recommendations, predictedImprovement: { latency: objective === 'latency' || objective === 'balanced' ? 25 : 10, throughput: objective === 'throughput' || objective === 'balanced' ? 30 : 15, cost: objective === 'cost' || objective === 'balanced' ? 20 : 5, }, }; } function getConfigInterpretation( improvement: { latency: number; throughput: number; cost: number }, objective: string ): string { const primary = objective === 'latency' ? improvement.latency : objective === 'throughput' ? improvement.throughput : objective === 'cost' ? improvement.cost : (improvement.latency + improvement.throughput) / 2; if (primary > 30) { return `Significant ${objective} improvement of ~${primary.toFixed(0)}% predicted with recommended changes`; } if (primary > 15) { return `Moderate ${objective} improvement of ~${primary.toFixed(0)}% expected`; } return `Minor ${objective} improvement of ~${primary.toFixed(0)}% possible. Configuration is already well-tuned`; } // ============================================================================ // Tool Accessor Functions // ============================================================================ /** * Get a tool by name */ export function getTool(name: string): MCPTool | undefined { return perfOptimizerTools.find(tool => tool.name === name); } /** * Get all tool names */ export function getToolNames(): string[] { return perfOptimizerTools.map(tool => tool.name); }