UNPKG

@claude-vector/core

Version:

Core vector search engine for code intelligence

554 lines (467 loc) 16.8 kB
/** * PerformanceProfiler - 詳細なパフォーマンス測定とボトルネック分析 * * 測定項目: * - 処理時間の詳細分解 * - メモリ使用量の追跡 * - ファイルI/O性能 * - API呼び出し効率 * - ボトルネック特定 */ import fs from 'fs/promises'; import path from 'path'; export class PerformanceProfiler { constructor(options = {}) { this.options = { enabled: options.enabled ?? true, detailLevel: options.detailLevel ?? 'standard', // 'basic', 'standard', 'detailed', 'profiling' outputFile: options.outputFile ?? './.claude-performance.json', realTimeReporting: options.realTimeReporting ?? false, memoryTracking: options.memoryTracking ?? true, ...options }; // 測定データ this.metrics = { sessions: [], currentSession: null, globalStats: { totalOperations: 0, totalTime: 0, averageMemoryUsage: 0, peakMemoryUsage: 0 } }; // 測定コンテキスト this.activeOperations = new Map(); this.memorySnapshots = []; // レポート生成設定 this.reportConfig = { showBottlenecks: true, showOptimizationTips: true, compareWithBaseline: false, baseline: null }; } /** * 測定セッション開始 */ startSession(sessionName, metadata = {}) { if (!this.options.enabled) return null; const sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const session = { id: sessionId, name: sessionName, startTime: Date.now(), startMemory: this.getMemoryUsage(), metadata, operations: [], endTime: null, summary: null }; this.metrics.currentSession = session; this.metrics.sessions.push(session); if (this.options.realTimeReporting) { console.log(`🔍 Performance profiling started: ${sessionName}`); } return sessionId; } /** * 操作の測定開始 */ startOperation(operationName, context = {}) { if (!this.options.enabled || !this.metrics.currentSession) return null; const operationId = `op_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`; const operation = { id: operationId, name: operationName, startTime: Date.now(), startMemory: this.getMemoryUsage(), context, endTime: null, duration: null, memoryDelta: null, subOperations: [], metrics: {} }; this.activeOperations.set(operationId, operation); // 詳細レベルに応じて追加測定 if (this.options.detailLevel === 'detailed' || this.options.detailLevel === 'profiling') { operation.startCPU = process.cpuUsage(); operation.startHRTime = process.hrtime.bigint(); } return operationId; } /** * 操作の測定終了 */ endOperation(operationId, additionalMetrics = {}) { if (!this.options.enabled || !operationId || !this.activeOperations.has(operationId)) { return null; } const operation = this.activeOperations.get(operationId); const endTime = Date.now(); const endMemory = this.getMemoryUsage(); // 基本測定値の計算 operation.endTime = endTime; operation.duration = endTime - operation.startTime; operation.memoryDelta = endMemory.heapUsed - operation.startMemory.heapUsed; operation.peakMemory = Math.max(endMemory.heapUsed, operation.startMemory.heapUsed); // 詳細測定値の計算 if (operation.startCPU) { const endCPU = process.cpuUsage(operation.startCPU); operation.cpuUsage = { user: endCPU.user, system: endCPU.system, total: endCPU.user + endCPU.system }; } if (operation.startHRTime) { operation.highResolutionDuration = Number(process.hrtime.bigint() - operation.startHRTime) / 1e6; // ナノ秒からミリ秒 } // 追加メトリクスの統合 operation.metrics = { ...operation.metrics, ...additionalMetrics }; // 現在のセッションに追加 if (this.metrics.currentSession) { this.metrics.currentSession.operations.push(operation); } this.activeOperations.delete(operationId); // リアルタイムレポート if (this.options.realTimeReporting) { this.reportOperation(operation); } return operation; } /** * サブ操作の測定(ネストした操作用) */ async measureSubOperation(parentOperationId, subOperationName, asyncFunction) { const subOpId = this.startOperation(`${subOperationName}`, { parent: parentOperationId }); try { const result = await asyncFunction(); const operation = this.endOperation(subOpId); // 親操作に追加 if (parentOperationId && this.activeOperations.has(parentOperationId)) { this.activeOperations.get(parentOperationId).subOperations.push(operation); } return result; } catch (error) { this.endOperation(subOpId, { error: error.message }); throw error; } } /** * ファイルI/O操作の測定 */ async measureFileIO(operationName, filePath, operation) { const startTime = Date.now(); const startMemory = this.getMemoryUsage(); const result = await operation(); const endTime = Date.now(); const endMemory = this.getMemoryUsage(); // ファイルサイズ取得 let fileSize = 0; try { const stats = await fs.stat(filePath); fileSize = stats.size; } catch (e) { // ファイルが存在しない場合は0 } const ioMetrics = { operation: operationName, filePath, fileSize, duration: endTime - startTime, memoryDelta: endMemory.heapUsed - startMemory.heapUsed, throughput: fileSize > 0 ? fileSize / (endTime - startTime) * 1000 : 0 // bytes/sec }; if (this.metrics.currentSession) { if (!this.metrics.currentSession.fileIO) { this.metrics.currentSession.fileIO = []; } this.metrics.currentSession.fileIO.push(ioMetrics); } return result; } /** * セッション終了 */ endSession(summary = null) { if (!this.options.enabled || !this.metrics.currentSession) return null; const session = this.metrics.currentSession; session.endTime = Date.now(); session.duration = session.endTime - session.startTime; session.endMemory = this.getMemoryUsage(); session.summary = summary || this.generateSessionSummary(session); // グローバル統計の更新 this.updateGlobalStats(session); // セッション終了 this.metrics.currentSession = null; // ファイル出力 if (this.options.outputFile) { this.saveMetrics(); } return session; } /** * メモリ使用量取得 */ getMemoryUsage() { const usage = process.memoryUsage(); return { heapUsed: usage.heapUsed, heapTotal: usage.heapTotal, external: usage.external, rss: usage.rss, timestamp: Date.now() }; } /** * セッションサマリー生成 */ generateSessionSummary(session) { const operations = session.operations || []; const totalDuration = session.duration || 0; const memoryDelta = session.endMemory ? session.endMemory.heapUsed - session.startMemory.heapUsed : 0; // 操作別統計 const operationStats = {}; operations.forEach(op => { if (!operationStats[op.name]) { operationStats[op.name] = { count: 0, totalDuration: 0, totalMemoryDelta: 0, avgDuration: 0, maxDuration: 0, minDuration: Infinity }; } const stats = operationStats[op.name]; stats.count++; stats.totalDuration += op.duration; stats.totalMemoryDelta += op.memoryDelta; stats.maxDuration = Math.max(stats.maxDuration, op.duration); stats.minDuration = Math.min(stats.minDuration, op.duration); }); // 平均値計算 Object.values(operationStats).forEach(stats => { stats.avgDuration = stats.totalDuration / stats.count; if (stats.minDuration === Infinity) stats.minDuration = 0; }); // ボトルネック検出 const bottlenecks = this.detectBottlenecks(operations); return { totalDuration, memoryDelta, operationsCount: operations.length, operationStats, bottlenecks, efficiency: this.calculateEfficiency(session), recommendations: this.generateRecommendations(session, bottlenecks) }; } /** * ボトルネック検出 */ detectBottlenecks(operations) { const bottlenecks = []; const totalDuration = operations.reduce((sum, op) => sum + op.duration, 0); operations.forEach(op => { const percentage = (op.duration / totalDuration) * 100; // 20%以上を占める操作をボトルネックとして検出 if (percentage >= 20) { bottlenecks.push({ operation: op.name, duration: op.duration, percentage: percentage.toFixed(1), memoryImpact: op.memoryDelta, severity: percentage >= 40 ? 'high' : 'medium' }); } }); return bottlenecks.sort((a, b) => b.percentage - a.percentage); } /** * 効率性計算 */ calculateEfficiency(session) { const operations = session.operations || []; if (operations.length === 0) return { score: 0, grade: 'N/A' }; // 各操作の効率性を評価 let totalEfficiency = 0; let operationCount = 0; operations.forEach(op => { // 時間効率(短いほど良い、基準値は1000ms) const timeEfficiency = Math.max(0, 100 - (op.duration / 1000) * 100); // メモリ効率(少ないほど良い、基準値は10MB) const memoryEfficiency = Math.max(0, 100 - (op.memoryDelta / (10 * 1024 * 1024)) * 100); // 総合効率 const efficiency = (timeEfficiency + memoryEfficiency) / 2; totalEfficiency += efficiency; operationCount++; }); const averageEfficiency = totalEfficiency / operationCount; let grade = 'F'; if (averageEfficiency >= 90) grade = 'A'; else if (averageEfficiency >= 80) grade = 'B'; else if (averageEfficiency >= 70) grade = 'C'; else if (averageEfficiency >= 60) grade = 'D'; return { score: Math.round(averageEfficiency), grade, timeEfficiency: Math.round(totalEfficiency / operationCount), memoryEfficiency: Math.round(totalEfficiency / operationCount) }; } /** * 最適化提案生成 */ generateRecommendations(session, bottlenecks) { const recommendations = []; // ボトルネック関連の提案 bottlenecks.forEach(bottleneck => { if (bottleneck.operation.includes('loadIndex') || bottleneck.operation.includes('load')) { recommendations.push({ type: 'optimization', priority: bottleneck.severity, operation: bottleneck.operation, issue: 'Index loading takes significant time', suggestion: 'Consider implementing streaming index loading or compression', expectedImprovement: '20-40% faster loading' }); } if (bottleneck.operation.includes('search')) { recommendations.push({ type: 'optimization', priority: bottleneck.severity, operation: bottleneck.operation, issue: 'Search operation is slow', suggestion: 'Implement result caching or optimize vector calculations', expectedImprovement: '15-30% faster search' }); } }); // メモリ使用量関連の提案 const totalMemoryDelta = session.endMemory ? session.endMemory.heapUsed - session.startMemory.heapUsed : 0; if (totalMemoryDelta > 100 * 1024 * 1024) { // 100MB以上 recommendations.push({ type: 'memory', priority: 'medium', issue: 'High memory usage detected', suggestion: 'Consider implementing memory pooling or streaming processing', expectedImprovement: '30-50% memory reduction' }); } return recommendations; } /** * 操作レポート */ reportOperation(operation) { const duration = operation.duration.toFixed(1); const memory = (operation.memoryDelta / 1024 / 1024).toFixed(1); console.log(`⏱️ ${operation.name}: ${duration}ms (${memory}MB)`); if (operation.subOperations && operation.subOperations.length > 0) { operation.subOperations.forEach(subOp => { const subDuration = subOp.duration.toFixed(1); const subMemory = (subOp.memoryDelta / 1024 / 1024).toFixed(1); console.log(` └─ ${subOp.name}: ${subDuration}ms (${subMemory}MB)`); }); } } /** * グローバル統計更新 */ updateGlobalStats(session) { const stats = this.metrics.globalStats; stats.totalOperations += session.operations ? session.operations.length : 0; stats.totalTime += session.duration || 0; if (session.endMemory) { const sessionMemory = session.endMemory.heapUsed; stats.peakMemoryUsage = Math.max(stats.peakMemoryUsage, sessionMemory); // 移動平均でメモリ使用量を更新 const alpha = 0.1; // 平滑化係数 stats.averageMemoryUsage = stats.averageMemoryUsage * (1 - alpha) + sessionMemory * alpha; } } /** * メトリクス保存 */ async saveMetrics() { try { const outputPath = path.resolve(this.options.outputFile); const data = { timestamp: new Date().toISOString(), config: this.options, metrics: this.metrics, version: '1.0.0' }; // BigInt を文字列に変換してJSONシリアライゼーション可能にする const jsonString = JSON.stringify(data, (key, value) => { if (typeof value === 'bigint') { return value.toString() + 'n'; } return value; }, 2); await fs.writeFile(outputPath, jsonString, 'utf8'); if (this.options.realTimeReporting) { console.log(`📊 Performance metrics saved to: ${outputPath}`); } } catch (error) { console.error('Failed to save performance metrics:', error); } } /** * 詳細レポート生成 */ generateDetailedReport() { const sessions = this.metrics.sessions; if (sessions.length === 0) { return 'No performance data available.'; } const latestSession = sessions[sessions.length - 1]; const summary = latestSession.summary; let report = '\n🔍 Performance Analysis Report\n'; report += '=' .repeat(50) + '\n\n'; // セッション概要 report += `📊 Session: ${latestSession.name}\n`; report += `⏱️ Duration: ${latestSession.duration}ms\n`; report += `💾 Memory Delta: ${(summary.memoryDelta / 1024 / 1024).toFixed(1)}MB\n`; report += `🔢 Operations: ${summary.operationsCount}\n`; report += `📈 Efficiency: ${summary.efficiency.score}/100 (${summary.efficiency.grade})\n\n`; // ボトルネック if (summary.bottlenecks.length > 0) { report += '🚨 Bottlenecks Detected:\n'; summary.bottlenecks.forEach(bottleneck => { report += ` • ${bottleneck.operation}: ${bottleneck.duration}ms (${bottleneck.percentage}%)\n`; }); report += '\n'; } // 最適化提案 if (summary.recommendations.length > 0) { report += '💡 Optimization Recommendations:\n'; summary.recommendations.forEach((rec, index) => { report += ` ${index + 1}. ${rec.issue}\n`; report += ` Suggestion: ${rec.suggestion}\n`; report += ` Expected: ${rec.expectedImprovement}\n\n`; }); } return report; } /** * パフォーマンス比較 */ compareWithBaseline(baselineSession) { if (!this.metrics.currentSession) return null; const current = this.metrics.currentSession; const comparison = { durationChange: ((current.duration - baselineSession.duration) / baselineSession.duration) * 100, memoryChange: ((current.summary.memoryDelta - baselineSession.summary.memoryDelta) / baselineSession.summary.memoryDelta) * 100, efficiencyChange: current.summary.efficiency.score - baselineSession.summary.efficiency.score }; return comparison; } } // デフォルトのプロファイラーインスタンス export const defaultProfiler = new PerformanceProfiler();