← back to Wine Finder Next

app/api/performance/metrics/route.ts

126 lines

import { NextRequest, NextResponse } from 'next/server';
import { performanceMonitor } from '@/lib/performance';
import { getRedisClient } from '@/lib/redis';

export async function GET(request: NextRequest) {
  try {
    // Get performance metrics
    const metrics = performanceMonitor.getAllStats();

    // Get Redis stats
    const redis = getRedisClient();
    let redisInfo: any = {};

    try {
      const info = await redis.info();
      const lines = info.split('\r\n');

      lines.forEach(line => {
        if (line && !line.startsWith('#')) {
          const [key, value] = line.split(':');
          if (key && value) {
            redisInfo[key] = value;
          }
        }
      });
    } catch (error) {
      console.error('Failed to get Redis info:', error);
    }

    // Calculate cache hit rate
    const cacheHits = parseInt(redisInfo.keyspace_hits || '0');
    const cacheMisses = parseInt(redisInfo.keyspace_misses || '0');
    const cacheHitRate = cacheHits + cacheMisses > 0
      ? (cacheHits / (cacheHits + cacheMisses) * 100).toFixed(2)
      : '0';

    // Memory usage
    const processMemory = process.memoryUsage();

    // Response time buckets
    const responseTimeBuckets = {
      fast: 0,    // < 100ms
      medium: 0,  // 100-500ms
      slow: 0,    // > 500ms
    };

    // Analyze API response times
    Object.entries(metrics).forEach(([key, stats]: [string, any]) => {
      if (key.startsWith('api.') && stats) {
        if (stats.median < 100) responseTimeBuckets.fast++;
        else if (stats.median < 500) responseTimeBuckets.medium++;
        else responseTimeBuckets.slow++;
      }
    });

    const performanceData = {
      timestamp: new Date().toISOString(),
      metrics: {
        api: metrics,
        cache: {
          hitRate: `${cacheHitRate}%`,
          hits: cacheHits,
          misses: cacheMisses,
          keys: parseInt(redisInfo.db0?.split(',')[0]?.split('=')[1] || '0'),
          memoryUsed: redisInfo.used_memory_human,
          evictedKeys: parseInt(redisInfo.evicted_keys || '0'),
        },
        memory: {
          rss: `${(processMemory.rss / 1024 / 1024).toFixed(2)} MB`,
          heapUsed: `${(processMemory.heapUsed / 1024 / 1024).toFixed(2)} MB`,
          heapTotal: `${(processMemory.heapTotal / 1024 / 1024).toFixed(2)} MB`,
          external: `${(processMemory.external / 1024 / 1024).toFixed(2)} MB`,
        },
        responseTimeBuckets,
        recommendations: generateRecommendations(metrics, redisInfo, cacheHitRate),
      },
    };

    return NextResponse.json(performanceData);
  } catch (error) {
    console.error('Error fetching performance metrics:', error);
    return NextResponse.json(
      { error: 'Failed to fetch performance metrics' },
      { status: 500 }
    );
  }
}

function generateRecommendations(metrics: any, redisInfo: any, cacheHitRate: string): string[] {
  const recommendations = [];

  // Cache hit rate recommendations
  if (parseFloat(cacheHitRate) < 80) {
    recommendations.push('Cache hit rate is below 80%. Consider increasing cache TTL or warming cache on startup.');
  }

  // Memory recommendations
  const usedMemory = parseInt(redisInfo.used_memory || '0');
  const maxMemory = parseInt(redisInfo.maxmemory || '0');
  if (maxMemory > 0 && usedMemory / maxMemory > 0.9) {
    recommendations.push('Redis memory usage is above 90%. Consider increasing max memory or optimizing cache keys.');
  }

  // API response time recommendations
  let slowApis: string[] = [];
  Object.entries(metrics).forEach(([key, stats]: [string, any]) => {
    if (key.startsWith('api.') && stats && stats.p95 > 500) {
      slowApis.push(key.replace('api.', ''));
    }
  });

  if (slowApis.length > 0) {
    recommendations.push(`APIs with slow p95 response times (>500ms): ${slowApis.join(', ')}. Consider optimizing these endpoints.`);
  }

  // Eviction recommendations
  if (parseInt(redisInfo.evicted_keys || '0') > 100) {
    recommendations.push('High number of evicted keys detected. Consider increasing Redis memory limit.');
  }

  if (recommendations.length === 0) {
    recommendations.push('Performance metrics look good! No immediate optimizations needed.');
  }

  return recommendations;
}