@claude-vector/core
Version:
Core vector search engine for code intelligence
554 lines (467 loc) • 16.8 kB
JavaScript
/**
* 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();