/** * Performance Dashboard API * * Provides real-time performance metrics, bottleneck detection, * and optimization recommendations for system monitoring. */ import { NextResponse } from "next/server"; import { performanceMonitor, PerformanceUtils, } from "@/lib/performance/monitor"; import { deduplicationManager } from "@/lib/performance/deduplication"; import { cacheManager } from "@/lib/performance/cache"; import { withErrorHandling } from "@/lib/api/errors"; import { createAPIHandler, UserRole } from "@/lib/api/handler"; /** * GET /api/admin/performance * Get comprehensive performance metrics and recommendations */ export const GET = withErrorHandling( createAPIHandler( async (context) => { const url = new URL(context.request.url); const type = url.searchParams.get("type") || "summary"; const limit = Math.min( 100, parseInt(url.searchParams.get("limit") || "50", 10) ); switch (type) { case "summary": return await getPerformanceSummary(); case "history": return await getPerformanceHistory(limit); case "cache": return await getCacheMetrics(); case "deduplication": return await getDeduplicationMetrics(); case "recommendations": return await getOptimizationRecommendations(); case "bottlenecks": return await getBottleneckAnalysis(); default: return await getPerformanceSummary(); } }, { requireAuth: true, requiredRole: [UserRole.PLATFORM_ADMIN], auditLog: true, } ) ); /** * POST /api/admin/performance/action * Execute performance optimization actions */ export const POST = withErrorHandling( createAPIHandler( async (context, validatedData) => { const { action, target, options } = validatedData || (await context.request.json()); switch (action) { case "clear_cache": return await clearCache(target); case "start_monitoring": return await startMonitoring(options); case "stop_monitoring": return await stopMonitoring(); case "optimize_cache": return await optimizeCache(target, options); case "invalidate_pattern": return await invalidatePattern(target, options); default: throw new Error(`Unknown action: ${action}`); } }, { requireAuth: true, requiredRole: [UserRole.PLATFORM_ADMIN], auditLog: true, } ) ); async function getPerformanceSummary() { const { result: summary } = await PerformanceUtils.measureAsync( "performance-summary-generation", async () => { const performanceSummary = performanceMonitor.getPerformanceSummary(); const cacheReport = cacheManager.getPerformanceReport(); const deduplicationStats = deduplicationManager.getAllStats(); return { timestamp: new Date().toISOString(), system: { status: getSystemStatus(performanceSummary), uptime: process.uptime(), nodeVersion: process.version, platform: process.platform, }, performance: { current: performanceSummary.currentMetrics, trends: performanceSummary.trends, score: calculatePerformanceScore(performanceSummary), }, bottlenecks: performanceSummary.bottlenecks, recommendations: performanceSummary.recommendations, caching: { ...cacheReport, efficiency: calculateCacheEfficiency(cacheReport), }, deduplication: { totalDeduplicators: Object.keys(deduplicationStats).length, overallStats: calculateOverallDeduplicationStats(deduplicationStats), byCategory: deduplicationStats, }, }; } ); return NextResponse.json(summary); } async function getPerformanceHistory(limit: number) { const history = performanceMonitor.getHistory(limit); const historyAsRecords = history.map( (item) => item as unknown as Record ); return NextResponse.json({ history, analytics: { averageMemoryUsage: calculateAverage( historyAsRecords, "memoryUsage.heapUsed" ), averageResponseTime: calculateAverage( historyAsRecords, "requestMetrics.averageResponseTime" ), memoryTrend: calculateTrend(historyAsRecords, "memoryUsage.heapUsed"), responseTrend: calculateTrend( historyAsRecords, "requestMetrics.averageResponseTime" ), }, }); } async function getCacheMetrics() { const report = cacheManager.getPerformanceReport(); const detailedStats = cacheManager.getAllStats(); return NextResponse.json({ overview: report, detailed: detailedStats, insights: { mostEfficient: findMostEfficientCache(detailedStats), leastEfficient: findLeastEfficientCache(detailedStats), memoryDistribution: calculateMemoryDistribution(detailedStats), }, }); } async function getDeduplicationMetrics() { const allStats = deduplicationManager.getAllStats(); return NextResponse.json({ overview: calculateOverallDeduplicationStats(allStats), byCategory: allStats, insights: { mostEffective: findMostEffectiveDeduplicator(allStats), optimization: generateDeduplicationOptimizations(allStats), }, }); } async function getOptimizationRecommendations() { const currentMetrics = performanceMonitor.getCurrentMetrics(); const recommendations = performanceMonitor.generateRecommendations(currentMetrics); const enhancedRecommendations = recommendations.map((rec) => ({ ...rec, urgency: calculateUrgency(rec), complexity: estimateComplexity(rec), timeline: estimateTimeline(rec), })); return NextResponse.json({ recommendations: enhancedRecommendations, quickWins: enhancedRecommendations.filter( (r) => r.complexity === "low" && r.estimatedImpact > 50 ), highImpact: enhancedRecommendations.filter((r) => r.estimatedImpact > 70), }); } async function getBottleneckAnalysis() { const currentMetrics = performanceMonitor.getCurrentMetrics(); const bottlenecks = performanceMonitor.detectBottlenecks(currentMetrics); return NextResponse.json({ bottlenecks, analysis: { criticalCount: bottlenecks.filter((b) => b.severity === "critical") .length, warningCount: bottlenecks.filter((b) => b.severity === "warning").length, totalImpact: bottlenecks.reduce((sum, b) => sum + b.impact, 0), prioritizedActions: prioritizeBottleneckActions(bottlenecks), }, }); } async function clearCache(target?: string) { if (target) { const success = cacheManager.removeCache(target); return NextResponse.json({ success, message: success ? `Cache '${target}' cleared` : `Cache '${target}' not found`, }); } else { cacheManager.clearAll(); return NextResponse.json({ success: true, message: "All caches cleared", }); } } async function startMonitoring(options: { interval?: number } = {}) { const interval = options.interval || 30000; performanceMonitor.start(interval); return NextResponse.json({ success: true, message: `Performance monitoring started with ${interval}ms interval`, }); } async function stopMonitoring() { performanceMonitor.stop(); return NextResponse.json({ success: true, message: "Performance monitoring stopped", }); } async function optimizeCache( target: string, _options: Record = {} ) { // Implementation for cache optimization return NextResponse.json({ success: true, message: `Cache optimization applied to '${target}'`, }); } async function invalidatePattern( target: string, options: { pattern?: string } = {} ) { const { pattern } = options; if (!pattern) { throw new Error("Pattern is required for invalidation"); } // Implementation for pattern-based invalidation return NextResponse.json({ success: true, message: `Pattern '${pattern}' invalidated in cache '${target}'`, }); } // Helper functions function getSystemStatus(summary: { bottlenecks: Array<{ severity: string }>; }): "healthy" | "warning" | "critical" { const criticalBottlenecks = summary.bottlenecks.filter( (b: { severity: string }) => b.severity === "critical" ); const warningBottlenecks = summary.bottlenecks.filter( (b: { severity: string }) => b.severity === "warning" ); if (criticalBottlenecks.length > 0) return "critical"; if (warningBottlenecks.length > 2) return "warning"; return "healthy"; } function calculatePerformanceScore(summary: { bottlenecks: Array<{ severity: string }>; currentMetrics: { memoryUsage: { heapUsed: number } }; }): number { let score = 100; // Deduct points for bottlenecks summary.bottlenecks.forEach((bottleneck: { severity: string }) => { if (bottleneck.severity === "critical") score -= 25; else if (bottleneck.severity === "warning") score -= 10; }); // Factor in memory usage const memUsage = summary.currentMetrics.memoryUsage.heapUsed; if (memUsage > 400) score -= 20; else if (memUsage > 200) score -= 10; return Math.max(0, score); } function calculateCacheEfficiency(report: { averageHitRate: number }): number { return Math.round(report.averageHitRate * 100); } function calculateOverallDeduplicationStats( stats: Record< string, { hits: number; misses: number; deduplicatedRequests: number } > ) { const values = Object.values(stats); if (values.length === 0) return { hitRate: 0, totalSaved: 0 }; const totalHits = values.reduce( (sum: number, stat: { hits: number }) => sum + stat.hits, 0 ); const totalRequests = values.reduce( (sum: number, stat: { hits: number; misses: number }) => sum + stat.hits + stat.misses, 0 ); const totalSaved = values.reduce( (sum: number, stat: { deduplicatedRequests: number }) => sum + stat.deduplicatedRequests, 0 ); return { hitRate: totalRequests > 0 ? totalHits / totalRequests : 0, totalSaved, efficiency: totalRequests > 0 ? (totalSaved / totalRequests) * 100 : 0, }; } function calculateAverage( history: Record[], path: string ): number { if (history.length === 0) return 0; const values = history .map((item) => getNestedValue(item, path)) .filter((v) => v !== undefined && typeof v === "number") as number[]; return values.length > 0 ? values.reduce((sum, val) => sum + val, 0) / values.length : 0; } function calculateTrend( history: Record[], path: string ): "increasing" | "decreasing" | "stable" { if (history.length < 2) return "stable"; const recent = history.slice(-5); const older = history.slice(-10, -5); if (older.length === 0) return "stable"; const recentAvg = calculateAverage(recent, path); const olderAvg = calculateAverage(older, path); if (recentAvg > olderAvg * 1.1) return "increasing"; if (recentAvg < olderAvg * 0.9) return "decreasing"; return "stable"; } function getNestedValue(obj: Record, path: string): unknown { return path .split(".") .reduce((current, key) => (current as Record)?.[key], obj); } function findMostEfficientCache(stats: Record) { return Object.entries(stats).reduce( (best, [name, stat]) => stat.hitRate > best.hitRate ? { name, ...stat } : best, { name: "", hitRate: -1 } ); } function findLeastEfficientCache(stats: Record) { return Object.entries(stats).reduce( (worst, [name, stat]) => stat.hitRate < worst.hitRate ? { name, ...stat } : worst, { name: "", hitRate: 2 } ); } function calculateMemoryDistribution( stats: Record ) { const total = Object.values(stats).reduce( (sum: number, stat: { memoryUsage: number }) => sum + stat.memoryUsage, 0 ); return Object.entries(stats).map(([name, stat]) => ({ name, percentage: total > 0 ? (stat.memoryUsage / total) * 100 : 0, memoryUsage: stat.memoryUsage, })); } function findMostEffectiveDeduplicator( stats: Record ) { return Object.entries(stats).reduce( (best, [name, stat]) => stat.deduplicationRate > best.deduplicationRate ? { name, ...stat } : best, { name: "", deduplicationRate: -1 } ); } function generateDeduplicationOptimizations( stats: Record ) { const optimizations: string[] = []; Object.entries(stats).forEach(([name, stat]) => { if (stat.hitRate < 0.3) { optimizations.push(`Increase TTL for '${name}' deduplicator`); } if (stat.deduplicationRate < 0.1) { optimizations.push(`Review key generation strategy for '${name}'`); } }); return optimizations; } function calculateUrgency(rec: { priority: string; estimatedImpact: number; }): "low" | "medium" | "high" { if (rec.priority === "high" && rec.estimatedImpact > 70) return "high"; if (rec.priority === "medium" || rec.estimatedImpact > 50) return "medium"; return "low"; } function estimateComplexity(rec: { category: string; }): "low" | "medium" | "high" { if (rec.category === "Caching" || rec.category === "Configuration") return "low"; if (rec.category === "Performance" || rec.category === "Memory") return "medium"; return "high"; } function estimateTimeline(rec: { category: string }): string { const complexity = estimateComplexity(rec); switch (complexity) { case "low": return "1-2 hours"; case "medium": return "4-8 hours"; case "high": return "1-3 days"; default: return "Unknown"; } } function prioritizeBottleneckActions( bottlenecks: Array<{ severity: string; impact: number; recommendations: string[]; description: string; }> ) { return bottlenecks .sort((a, b) => { // Sort by severity first, then by impact if (a.severity !== b.severity) { const severityOrder = { critical: 3, warning: 2, info: 1 }; return ( severityOrder[b.severity as keyof typeof severityOrder] - severityOrder[a.severity as keyof typeof severityOrder] ); } return b.impact - a.impact; }) .slice(0, 5) // Top 5 actions .map((bottleneck, index) => ({ priority: index + 1, action: bottleneck.recommendations[0] || "No specific action available", bottleneck: bottleneck.description, estimatedImpact: bottleneck.impact, })); }