🚀 Oferta especial: 60% OFF no CrazyStack - Últimas vagas!Garantir vaga →
Módulo 4 - Aula 5

Performance Testing

Domine performance testing para tRPC: load testing avançado, stress testing, memory profiling, Core Web Vitals e otimização completa para SaaS enterprise-grade.

95 min
Avançado
Performance

🎯 Por que performance testing é crucial para SaaS?

Retenção de Usuários: Cada 100ms de latência reduz conversões em 1% e aumenta abandono em aplicações SaaS.

Escalabilidade: Testes rigorosos revelam gargalos antes que afetem milhares de usuários em produção.

🎯 Load Testing com Artillery

artillery.config.yaml
// 📁 artillery.config.yaml
config:
  target: 'http://localhost:3000'
  phases:
    # 🔥 Fase de warmup
    - duration: 60
      arrivalRate: 5
      name: "Warmup"
    
    # ⚡ Carga normal
    - duration: 300
      arrivalRate: 25
      name: "Normal Load"
    
    # 🚀 Pico de tráfego
    - duration: 180
      arrivalRate: 100
      name: "Peak Traffic"
    
    # 📊 Carga sustentada
    - duration: 600
      arrivalRate: 50
      name: "Sustained Load"

  # 🎯 Métricas e thresholds
  ensure:
    - p95: 500  # 95% das requests < 500ms
    - p99: 1000 # 99% das requests < 1s
    - medianResponseTime: 200
    - maxErrorRate: 1

scenarios:
  # 👤 Cenário de usuário típico
  - name: "User Journey"
    weight: 70
    flow:
      # 🔐 Login
      - post:
          url: "/api/trpc/auth.login"
          json:
            email: "{{ $randomEmail() }}"
            password: "test123"
          capture:
            - json: "$.result.data.token"
              as: "authToken"
      
      # 📊 Dashboard inicial
      - get:
          url: "/api/trpc/dashboard.getOverview"
          headers:
            Authorization: "Bearer {{ authToken }}"
          expect:
            - statusCode: 200
            - contentType: json
      
      # 🏢 Dados da organização
      - get:
          url: "/api/trpc/organization.getProfile"
          headers:
            Authorization: "Bearer {{ authToken }}"
          expect:
            - statusCode: 200
      
      # 📋 Lista de usuários (paginada)
      - get:
          url: "/api/trpc/users.getList?input={{ encodeURIComponent(JSON.stringify({ page: {{ $randomInt(1, 10) }}, limit: 20 })) }}"
          headers:
            Authorization: "Bearer {{ authToken }}"
          expect:
            - statusCode: 200
      
      # 📈 Analytics
      - get:
          url: "/api/trpc/analytics.getMetrics"
          headers:
            Authorization: "Bearer {{ authToken }}"
          expect:
            - statusCode: 200

  # 🔍 Cenário de busca intensiva
  - name: "Search Heavy"
    weight: 20
    flow:
      - post:
          url: "/api/trpc/auth.login"
          json:
            email: "{{ $randomEmail() }}"
            password: "test123"
          capture:
            - json: "$.result.data.token"
              as: "authToken"
      
      # 🔍 Múltiplas buscas
      - loop:
          count: 5
          over:
            - get:
                url: "/api/trpc/search.query?input={{ encodeURIComponent(JSON.stringify({ q: '{{ $randomString() }}' })) }}"
                headers:
                  Authorization: "Bearer {{ authToken }}"

  # 📊 Cenário de relatórios
  - name: "Heavy Reports"
    weight: 10
    flow:
      - post:
          url: "/api/trpc/auth.login"
          json:
            email: "admin@test.com"
            password: "admin123"
          capture:
            - json: "$.result.data.token"
              as: "authToken"
      
      # 📈 Relatórios pesados
      - get:
          url: "/api/trpc/reports.generate"
          headers:
            Authorization: "Bearer {{ authToken }}"
          json:
            type: "full_analytics"
            period: "30d"
            includeCharts: true

// 📁 performance-tests/artillery-extensions.js
// 🔧 Extensões customizadas para Artillery

class TRPCPlugin {
  constructor(config, events) {
    this.config = config;
    this.events = events;
    
    // 📊 Métricas customizadas
    this.customMetrics = {
      trcpErrors: 0,
      rateLimitHits: 0,
      cacheHits: 0,
      cacheMisses: 0,
    };
    
    // 🎯 Interceptar responses
    events.on('response', (req, res, context) => {
      this.trackCustomMetrics(req, res, context);
    });
    
    // 📈 Relatório final
    events.on('done', (stats) => {
      this.generateReport(stats);
    });
  }
  
  trackCustomMetrics(req, res, context) {
    try {
      const body = JSON.parse(res.body);
      
      // 🚫 Erros tRPC
      if (body.error) {
        this.customMetrics.trcpErrors++;
        
        if (body.error.code === 'TOO_MANY_REQUESTS') {
          this.customMetrics.rateLimitHits++;
        }
      }
      
      // 💾 Cache headers
      const cacheHeader = res.headers['x-cache'];
      if (cacheHeader === 'HIT') {
        this.customMetrics.cacheHits++;
      } else if (cacheHeader === 'MISS') {
        this.customMetrics.cacheMisses++;
      }
      
    } catch (error) {
      // Ignora erros de parsing
    }
  }
  
  generateReport(stats) {
    console.log('\n🎯 tRPC Performance Report:');
    console.log(`📊 Total tRPC Errors: ${this.customMetrics.trcpErrors}`);
    console.log(`🚫 Rate Limit Hits: ${this.customMetrics.rateLimitHits}`);
    console.log(`💾 Cache Hit Rate: ${((this.customMetrics.cacheHits / (this.customMetrics.cacheHits + this.customMetrics.cacheMisses)) * 100).toFixed(2)}%`);
    
    // 🎯 Gerar insights automáticos
    if (this.customMetrics.rateLimitHits > 10) {
      console.log('⚠️  WARNING: High rate limit hits detected. Consider adjusting limits.');
    }
    
    const cacheHitRate = this.customMetrics.cacheHits / (this.customMetrics.cacheHits + this.customMetrics.cacheMisses);
    if (cacheHitRate < 0.8) {
      console.log('💾 WARNING: Low cache hit rate. Review caching strategy.');
    }
  }
}

module.exports = {
  TRPCPlugin
};

⚡ K6 Advanced Performance Testing

performance-tests/k6-trpc-test.js
// 📁 performance-tests/k6-trpc-test.js
import http from 'k6/http';
import { check, sleep, group } from 'k6';
import { Rate, Trend, Counter } from 'k6/metrics';

// 📊 Métricas customizadas
const errorRate = new Rate('errors');
const trcpResponseTime = new Trend('trpc_response_time');
const rateLimitCounter = new Counter('rate_limit_hits');
const cacheHitRate = new Rate('cache_hits');

// ⚙️ Configuração do teste
export const options = {
  stages: [
    // 🔥 Ramp-up gradual
    { duration: '2m', target: 20 },   // Subir para 20 usuários
    { duration: '5m', target: 50 },   // Subir para 50 usuários
    { duration: '10m', target: 100 }, // Subir para 100 usuários
    { duration: '5m', target: 200 },  // Pico: 200 usuários
    { duration: '10m', target: 100 }, // Reduzir para 100
    { duration: '5m', target: 0 },    // Ramp-down
  ],
  
  // 🎯 Thresholds de performance
  thresholds: {
    'http_req_duration': ['p(95)<500'], // 95% < 500ms
    'http_req_duration{name:login}': ['p(95)<300'],
    'http_req_duration{name:dashboard}': ['p(95)<400'],
    'http_req_duration{name:search}': ['p(95)<600'],
    'errors': ['rate<0.05'], // < 5% de erros
    'rate_limit_hits': ['count<100'], // < 100 rate limits
  },
};

// 🎯 Setup do teste
export function setup() {
  // 🔧 Criar usuários de teste
  const users = [];
  for (let i = 0; i < 50; i++) {
    users.push({
      email: `testuser${i}@performance.test`,
      password: 'test123',
    });
  }
  
  return { users };
}

// 🚀 Função principal do teste
export default function(data) {
  const user = data.users[Math.floor(Math.random() * data.users.length)];
  let authToken = null;
  
  group('Authentication Flow', () => {
    // 🔐 Login
    const loginResponse = http.post(
      'http://localhost:3000/api/trpc/auth.login',
      JSON.stringify(user),
      {
        headers: { 'Content-Type': 'application/json' },
        tags: { name: 'login' },
      }
    );
    
    const loginSuccess = check(loginResponse, {
      'login status is 200': (r) => r.status === 200,
      'login response time < 300ms': (r) => r.timings.duration < 300,
      'login returns token': (r) => {
        try {
          const body = JSON.parse(r.body);
          authToken = body.result?.data?.token;
          return !!authToken;
        } catch {
          return false;
        }
      },
    });
    
    errorRate.add(!loginSuccess);
    trcpResponseTime.add(loginResponse.timings.duration);
  });
  
  if (!authToken) return; // Falha no login, parar execução
  
  group('Dashboard Operations', () => {
    // 📊 Dashboard overview
    const dashboardResponse = http.get(
      'http://localhost:3000/api/trpc/dashboard.getOverview',
      {
        headers: { 'Authorization': `Bearer ${authToken}` },
        tags: { name: 'dashboard' },
      }
    );
    
    const dashboardSuccess = check(dashboardResponse, {
      'dashboard status is 200': (r) => r.status === 200,
      'dashboard response time < 400ms': (r) => r.timings.duration < 400,
      'dashboard has metrics': (r) => {
        try {
          const body = JSON.parse(r.body);
          return body.result?.data?.metrics !== undefined;
        } catch {
          return false;
        }
      },
    });
    
    // 💾 Verificar cache hit
    const cacheHeader = dashboardResponse.headers['X-Cache'];
    if (cacheHeader) {
      cacheHitRate.add(cacheHeader === 'HIT');
    }
    
    errorRate.add(!dashboardSuccess);
    trcpResponseTime.add(dashboardResponse.timings.duration);
    
    sleep(1); // Simular tempo de leitura
  });
  
  group('Search Operations', () => {
    // 🔍 Busca de usuários
    const searchQueries = ['admin', 'user', 'test', 'manager', 'developer'];
    const query = searchQueries[Math.floor(Math.random() * searchQueries.length)];
    
    const searchResponse = http.get(
      `http://localhost:3000/api/trpc/search.users?input=${encodeURIComponent(JSON.stringify({ q: query, limit: 20 }))}`,
      {
        headers: { 'Authorization': `Bearer ${authToken}` },
        tags: { name: 'search' },
      }
    );
    
    const searchSuccess = check(searchResponse, {
      'search status is 200': (r) => r.status === 200,
      'search response time < 600ms': (r) => r.timings.duration < 600,
      'search returns results': (r) => {
        try {
          const body = JSON.parse(r.body);
          return Array.isArray(body.result?.data?.users);
        } catch {
          return false;
        }
      },
    });
    
    // 🚫 Verificar rate limiting
    if (searchResponse.status === 429) {
      rateLimitCounter.add(1);
    }
    
    errorRate.add(!searchSuccess);
    trcpResponseTime.add(searchResponse.timings.duration);
    
    sleep(2); // Simular análise dos resultados
  });
  
  group('Heavy Operations', () => {
    // 📈 Operação pesada (apenas 20% dos usuários)
    if (Math.random() < 0.2) {
      const reportResponse = http.post(
        'http://localhost:3000/api/trpc/reports.generate',
        JSON.stringify({
          type: 'user_analytics',
          period: '7d',
          format: 'summary',
        }),
        {
          headers: { 
            'Authorization': `Bearer ${authToken}`,
            'Content-Type': 'application/json',
          },
          tags: { name: 'heavy_report' },
          timeout: '30s', // Timeout maior para operações pesadas
        }
      );
      
      check(reportResponse, {
        'report generation successful': (r) => r.status === 200,
        'report response time < 5s': (r) => r.timings.duration < 5000,
      });
      
      trcpResponseTime.add(reportResponse.timings.duration);
    }
    
    sleep(3);
  });
}

// 📊 Função de teardown
export function teardown(data) {
  console.log('🎯 Performance Test Completed');
  console.log(`👥 Total Users Tested: ${data.users.length}`);
}

// 📁 performance-tests/stress-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';

// 💥 Teste de stress - encontrar o ponto de quebra
export const options = {
  stages: [
    { duration: '2m', target: 100 },   // Ramp-up normal
    { duration: '5m', target: 100 },   // Manter carga
    { duration: '2m', target: 200 },   // Aumentar para 200
    { duration: '5m', target: 200 },   // Manter
    { duration: '2m', target: 300 },   // Aumentar para 300
    { duration: '5m', target: 300 },   // Manter
    { duration: '2m', target: 400 },   // Continuar aumentando
    { duration: '5m', target: 400 },   // Manter até quebrar
    { duration: '10m', target: 0 },    // Ramp-down
  ],
  
  thresholds: {
    'http_req_duration': ['p(99)<2000'], // Threshold mais relaxado
    'http_req_failed': ['rate<0.1'],     // Até 10% de falhas
  },
};

export default function() {
  const responses = http.batch([
    ['GET', 'http://localhost:3000/api/health'],
    ['GET', 'http://localhost:3000/api/trpc/public.getStats'],
    ['POST', 'http://localhost:3000/api/trpc/auth.refresh', '{}'],
  ]);
  
  check(responses[0], {
    'health check successful': (r) => r.status === 200,
  });
  
  sleep(1);
}

🧠 Memory e CPU Profiling

performance-tests/memory-profiling.js
// 📁 performance-tests/memory-profiling.js
import { execSync } from 'child_process';
import { writeFileSync } from 'fs';
import { performance, PerformanceObserver } from 'perf_hooks';

// 🔧 Classe para profiling de performance
class PerformanceProfiler {
  constructor() {
    this.measurements = [];
    this.memorySnapshots = [];
    this.gcEvents = [];
    
    this.setupObservers();
  }
  
  // 📊 Configurar observadores de performance
  setupObservers() {
    // 🔍 Observer para medições de tempo
    const perfObserver = new PerformanceObserver((list) => {
      list.getEntries().forEach((entry) => {
        this.measurements.push({
          name: entry.name,
          duration: entry.duration,
          startTime: entry.startTime,
          timestamp: Date.now(),
        });
      });
    });
    
    perfObserver.observe({ entryTypes: ['measure'] });
    
    // 🗑️ Observer para Garbage Collection
    const gcObserver = new PerformanceObserver((list) => {
      list.getEntries().forEach((entry) => {
        this.gcEvents.push({
          kind: entry.detail?.kind,
          duration: entry.duration,
          timestamp: Date.now(),
        });
      });
    });
    
    gcObserver.observe({ entryTypes: ['gc'] });
  }
  
  // 📸 Capturar snapshot de memória
  captureMemorySnapshot(label = 'snapshot') {
    const memUsage = process.memoryUsage();
    const snapshot = {
      label,
      timestamp: Date.now(),
      rss: memUsage.rss,                    // Resident Set Size
      heapTotal: memUsage.heapTotal,        // Total heap alocado
      heapUsed: memUsage.heapUsed,          // Heap em uso
      external: memUsage.external,          // Memória externa (C++)
      arrayBuffers: memUsage.arrayBuffers,  // ArrayBuffers
    };
    
    this.memorySnapshots.push(snapshot);
    return snapshot;
  }
  
  // ⏱️ Medir tempo de execução de função
  async measureFunction(name, fn) {
    const startMark = `${name}-start`;
    const endMark = `${name}-end`;
    
    performance.mark(startMark);
    const memBefore = this.captureMemorySnapshot(`${name}-before`);
    
    let result, error;
    try {
      result = await fn();
    } catch (err) {
      error = err;
    }
    
    performance.mark(endMark);
    const memAfter = this.captureMemorySnapshot(`${name}-after`);
    
    performance.measure(name, startMark, endMark);
    
    // 📊 Calcular diferença de memória
    const memoryDiff = {
      rss: memAfter.rss - memBefore.rss,
      heapUsed: memAfter.heapUsed - memBefore.heapUsed,
      heapTotal: memAfter.heapTotal - memBefore.heapTotal,
    };
    
    if (error) throw error;
    
    return {
      result,
      memoryDiff,
      duration: this.measurements[this.measurements.length - 1]?.duration,
    };
  }
  
  // 📈 Gerar relatório de performance
  generateReport() {
    const report = {
      timestamp: new Date().toISOString(),
      measurements: this.measurements,
      memorySnapshots: this.memorySnapshots,
      gcEvents: this.gcEvents,
      analysis: this.analyzePerformance(),
    };
    
    const reportPath = `./performance-report-${Date.now()}.json`;
    writeFileSync(reportPath, JSON.stringify(report, null, 2));
    
    console.log(`📊 Performance report saved to: ${reportPath}`);
    return report;
  }
  
  // 🔍 Analisar dados de performance
  analyzePerformance() {
    const analysis = {
      totalMeasurements: this.measurements.length,
      totalGCEvents: this.gcEvents.length,
      memoryTrends: this.analyzeMemoryTrends(),
      slowestOperations: this.findSlowestOperations(),
      gcAnalysis: this.analyzeGarbageCollection(),
    };
    
    return analysis;
  }
  
  analyzeMemoryTrends() {
    if (this.memorySnapshots.length < 2) return null;
    
    const first = this.memorySnapshots[0];
    const last = this.memorySnapshots[this.memorySnapshots.length - 1];
    
    return {
      heapGrowth: last.heapUsed - first.heapUsed,
      totalMemoryGrowth: last.rss - first.rss,
      peakHeapUsed: Math.max(...this.memorySnapshots.map(s => s.heapUsed)),
      peakTotalMemory: Math.max(...this.memorySnapshots.map(s => s.rss)),
    };
  }
  
  findSlowestOperations(limit = 10) {
    return this.measurements
      .sort((a, b) => b.duration - a.duration)
      .slice(0, limit)
      .map(m => ({
        name: m.name,
        duration: m.duration,
        timestamp: m.timestamp,
      }));
  }
  
  analyzeGarbageCollection() {
    if (this.gcEvents.length === 0) return null;
    
    const totalGCTime = this.gcEvents.reduce((sum, event) => sum + event.duration, 0);
    const avgGCTime = totalGCTime / this.gcEvents.length;
    
    return {
      totalEvents: this.gcEvents.length,
      totalTime: totalGCTime,
      averageTime: avgGCTime,
      longestGC: Math.max(...this.gcEvents.map(e => e.duration)),
    };
  }
}

// 🧪 Exemplo de uso do profiler
async function runPerformanceTests() {
  const profiler = new PerformanceProfiler();
  
  // 🔧 Simular operações tRPC
  const mockTRPCOperations = {
    async getUserList() {
      // Simular query complexa
      const users = [];
      for (let i = 0; i < 10000; i++) {
        users.push({
          id: `user-${i}`,
          name: `User ${i}`,
          email: `user${i}@test.com`,
          metadata: { someData: new Array(100).fill('data') },
        });
      }
      return users;
    },
    
    async generateReport() {
      // Simular processamento pesado
      const data = new Array(50000).fill(0).map((_, i) => ({
        id: i,
        value: Math.random() * 1000,
        category: `cat-${i % 10}`,
      }));
      
      // Processamento intensivo
      const processed = data
        .filter(item => item.value > 500)
        .sort((a, b) => b.value - a.value)
        .slice(0, 1000);
      
      return processed;
    },
    
    async searchUsers(query) {
      // Simular busca com regex
      const users = await this.getUserList();
      return users.filter(user => 
        user.name.toLowerCase().includes(query.toLowerCase())
      );
    },
  };
  
  console.log('🚀 Starting performance tests...');
  
  // 📊 Teste 1: Lista de usuários
  const userListResult = await profiler.measureFunction(
    'getUserList',
    () => mockTRPCOperations.getUserList()
  );
  console.log(`👥 User list: ${userListResult.duration.toFixed(2)}ms, Memory: ${(userListResult.memoryDiff.heapUsed / 1024 / 1024).toFixed(2)}MB`);
  
  // 📈 Teste 2: Geração de relatório
  const reportResult = await profiler.measureFunction(
    'generateReport',
    () => mockTRPCOperations.generateReport()
  );
  console.log(`📈 Report generation: ${reportResult.duration.toFixed(2)}ms, Memory: ${(reportResult.memoryDiff.heapUsed / 1024 / 1024).toFixed(2)}MB`);
  
  // 🔍 Teste 3: Busca
  const searchResult = await profiler.measureFunction(
    'searchUsers',
    () => mockTRPCOperations.searchUsers('test')
  );
  console.log(`🔍 Search: ${searchResult.duration.toFixed(2)}ms, Memory: ${(searchResult.memoryDiff.heapUsed / 1024 / 1024).toFixed(2)}MB`);
  
  // 🔄 Teste de stress com múltiplas operações
  console.log('\n🔥 Running stress test...');
  for (let i = 0; i < 50; i++) {
    await profiler.measureFunction(
      `stress-iteration-${i}`,
      async () => {
        await Promise.all([
          mockTRPCOperations.getUserList(),
          mockTRPCOperations.searchUsers('admin'),
          mockTRPCOperations.generateReport(),
        ]);
      }
    );
    
    if (i % 10 === 0) {
      profiler.captureMemorySnapshot(`stress-checkpoint-${i}`);
      
      // 🗑️ Forçar garbage collection para liberar memória
      if (global.gc) {
        global.gc();
      }
    }
  }
  
  // 📊 Gerar relatório final
  const report = profiler.generateReport();
  
  // 📈 Exibir resumo
  console.log('\n📊 Performance Summary:');
  console.log(`Total Operations: ${report.analysis.totalMeasurements}`);
  console.log(`Slowest Operations:`);
  report.analysis.slowestOperations.slice(0, 5).forEach((op, i) => {
    console.log(`  ${i + 1}. ${op.name}: ${op.duration.toFixed(2)}ms`);
  });
  
  if (report.analysis.memoryTrends) {
    console.log(`\nMemory Analysis:`);
    console.log(`  Heap Growth: ${(report.analysis.memoryTrends.heapGrowth / 1024 / 1024).toFixed(2)}MB`);
    console.log(`  Peak Memory: ${(report.analysis.memoryTrends.peakTotalMemory / 1024 / 1024).toFixed(2)}MB`);
  }
  
  if (report.analysis.gcAnalysis) {
    console.log(`\nGarbage Collection:`);
    console.log(`  Total GC Events: ${report.analysis.gcAnalysis.totalEvents}`);
    console.log(`  Total GC Time: ${report.analysis.gcAnalysis.totalTime.toFixed(2)}ms`);
    console.log(`  Average GC Time: ${report.analysis.gcAnalysis.averageTime.toFixed(2)}ms`);
  }
  
  return report;
}

// 🚀 Executar se for script principal
if (import.meta.url === `file://${process.argv[1]}`) {
  runPerformanceTests().catch(console.error);
}

export { PerformanceProfiler, runPerformanceTests };

🌐 Core Web Vitals e Frontend Performance

performance-tests/web-vitals-monitoring.ts
// 📁 performance-tests/web-vitals-monitoring.ts
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';

// 🎯 Interface para métricas de Web Vitals
interface WebVitalsMetric {
  name: string;
  value: number;
  rating: 'good' | 'needs-improvement' | 'poor';
  timestamp: number;
  id: string;
  url: string;
  userAgent: string;
}

// 📊 Classe para monitoramento de Web Vitals
class WebVitalsMonitor {
  private metrics: WebVitalsMetric[] = [];
  private apiEndpoint: string;
  
  constructor(apiEndpoint = '/api/analytics/web-vitals') {
    this.apiEndpoint = apiEndpoint;
    this.setupMetricsCollection();
  }
  
  // 🔧 Configurar coleta de métricas
  private setupMetricsCollection() {
    // 🎨 Largest Contentful Paint (LCP)
    getLCP((metric) => {
      this.recordMetric('LCP', metric);
    });
    
    // ⚡ First Input Delay (FID)
    getFID((metric) => {
      this.recordMetric('FID', metric);
    });
    
    // 📏 Cumulative Layout Shift (CLS)
    getCLS((metric) => {
      this.recordMetric('CLS', metric);
    });
    
    // 🎭 First Contentful Paint (FCP)
    getFCP((metric) => {
      this.recordMetric('FCP', metric);
    });
    
    // 🌐 Time to First Byte (TTFB)
    getTTFB((metric) => {
      this.recordMetric('TTFB', metric);
    });
  }
  
  // 📊 Registrar métrica
  private recordMetric(name: string, metric: any) {
    const webVitalMetric: WebVitalsMetric = {
      name,
      value: metric.value,
      rating: this.getRating(name, metric.value),
      timestamp: Date.now(),
      id: metric.id,
      url: window.location.href,
      userAgent: navigator.userAgent,
    };
    
    this.metrics.push(webVitalMetric);
    this.sendMetricToAPI(webVitalMetric);
    
    // 📈 Log para debug
    console.log(`📊 ${name}: ${metric.value} (${webVitalMetric.rating})`);
  }
  
  // 🎯 Determinar rating da métrica
  private getRating(name: string, value: number): 'good' | 'needs-improvement' | 'poor' {
    const thresholds = {
      LCP: { good: 2500, poor: 4000 },      // ms
      FID: { good: 100, poor: 300 },        // ms
      CLS: { good: 0.1, poor: 0.25 },       // score
      FCP: { good: 1800, poor: 3000 },      // ms
      TTFB: { good: 800, poor: 1800 },      // ms
    };
    
    const threshold = thresholds[name as keyof typeof thresholds];
    if (!threshold) return 'good';
    
    if (value <= threshold.good) return 'good';
    if (value <= threshold.poor) return 'needs-improvement';
    return 'poor';
  }
  
  // 📤 Enviar métrica para API
  private async sendMetricToAPI(metric: WebVitalsMetric) {
    try {
      await fetch(this.apiEndpoint, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(metric),
      });
    } catch (error) {
      console.warn('Failed to send metric to API:', error);
    }
  }
  
  // 📊 Obter relatório de métricas
  getMetricsReport() {
    const report = {
      timestamp: new Date().toISOString(),
      url: window.location.href,
      metrics: this.metrics,
      summary: this.generateSummary(),
    };
    
    return report;
  }
  
  // 📈 Gerar resumo das métricas
  private generateSummary() {
    const summary: any = {};
    
    ['LCP', 'FID', 'CLS', 'FCP', 'TTFB'].forEach(metricName => {
      const metricData = this.metrics.filter(m => m.name === metricName);
      if (metricData.length === 0) return;
      
      const values = metricData.map(m => m.value);
      const ratings = metricData.map(m => m.rating);
      
      summary[metricName] = {
        latest: values[values.length - 1],
        average: values.reduce((sum, val) => sum + val, 0) / values.length,
        min: Math.min(...values),
        max: Math.max(...values),
        rating: ratings[ratings.length - 1],
        samples: metricData.length,
      };
    });
    
    return summary;
  }
}

// 📁 performance-tests/lighthouse-automation.js
const lighthouse = require('lighthouse');
const chromeLauncher = require('chrome-launcher');
const fs = require('fs');

// 🔍 Classe para automação do Lighthouse
class LighthouseAutomation {
  constructor() {
    this.config = {
      extends: 'lighthouse:default',
      settings: {
        formFactor: 'desktop',
        throttling: {
          rttMs: 40,
          throughputKbps: 10240,
          cpuSlowdownMultiplier: 1,
        },
        screenEmulation: {
          mobile: false,
          width: 1920,
          height: 1080,
          deviceScaleFactor: 1,
        },
        auditMode: false,
        gatherMode: false,
        disableStorageReset: false,
      },
    };
  }
  
  // 🚀 Executar audit do Lighthouse
  async runAudit(url, options = {}) {
    console.log(`🔍 Starting Lighthouse audit for: ${url}`);
    
    const chrome = await chromeLauncher.launch({
      chromeFlags: ['--headless', '--no-sandbox', '--disable-gpu'],
    });
    
    try {
      const result = await lighthouse(url, {
        port: chrome.port,
        ...options,
      }, this.config);
      
      const report = this.processLighthouseResult(result);
      
      // 💾 Salvar relatório
      const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
      const filename = `lighthouse-report-${timestamp}.json`;
      fs.writeFileSync(filename, JSON.stringify(report, null, 2));
      
      console.log(`📊 Lighthouse report saved: ${filename}`);
      
      return report;
    } finally {
      await chrome.kill();
    }
  }
  
  // 📊 Processar resultado do Lighthouse
  processLighthouseResult(result) {
    const { lhr } = result;
    
    const coreMetrics = {
      performanceScore: Math.round(lhr.categories.performance.score * 100),
      accessibilityScore: Math.round(lhr.categories.accessibility.score * 100),
      bestPracticesScore: Math.round(lhr.categories['best-practices'].score * 100),
      seoScore: Math.round(lhr.categories.seo.score * 100),
    };
    
    const webVitals = {
      LCP: lhr.audits['largest-contentful-paint']?.numericValue,
      FID: lhr.audits['max-potential-fid']?.numericValue,
      CLS: lhr.audits['cumulative-layout-shift']?.numericValue,
      FCP: lhr.audits['first-contentful-paint']?.numericValue,
      TTI: lhr.audits['interactive']?.numericValue,
      TBT: lhr.audits['total-blocking-time']?.numericValue,
      Speed: lhr.audits['speed-index']?.numericValue,
    };
    
    const opportunities = lhr.audits['opportunities'] || [];
    const diagnostics = lhr.audits['diagnostics'] || [];
    
    return {
      url: lhr.finalUrl,
      timestamp: new Date().toISOString(),
      scores: coreMetrics,
      webVitals,
      opportunities: this.extractOpportunities(lhr),
      diagnostics: this.extractDiagnostics(lhr),
      recommendations: this.generateRecommendations(coreMetrics, webVitals),
    };
  }
  
  // 🔧 Extrair oportunidades de otimização
  extractOpportunities(lhr) {
    const opportunityAudits = [
      'unused-css-rules',
      'unused-javascript',
      'modern-image-formats',
      'render-blocking-resources',
      'unminified-css',
      'unminified-javascript',
      'efficient-animated-content',
      'duplicated-javascript',
    ];
    
    return opportunityAudits
      .map(auditId => {
        const audit = lhr.audits[auditId];
        if (!audit || audit.score === 1) return null;
        
        return {
          id: auditId,
          title: audit.title,
          description: audit.description,
          impact: audit.details?.overallSavingsMs || 0,
          score: audit.score,
        };
      })
      .filter(Boolean)
      .sort((a, b) => b.impact - a.impact);
  }
  
  // 🩺 Extrair diagnósticos
  extractDiagnostics(lhr) {
    const diagnosticAudits = [
      'server-response-time',
      'dom-size',
      'critical-request-chains',
      'main-thread-tasks',
      'third-party-summary',
    ];
    
    return diagnosticAudits
      .map(auditId => {
        const audit = lhr.audits[auditId];
        if (!audit) return null;
        
        return {
          id: auditId,
          title: audit.title,
          description: audit.description,
          score: audit.score,
          value: audit.displayValue,
        };
      })
      .filter(Boolean);
  }
  
  // 💡 Gerar recomendações
  generateRecommendations(scores, webVitals) {
    const recommendations = [];
    
    if (scores.performanceScore < 80) {
      recommendations.push({
        category: 'Performance',
        priority: 'high',
        message: 'Performance score below 80. Focus on Core Web Vitals optimization.',
      });
    }
    
    if (webVitals.LCP > 2500) {
      recommendations.push({
        category: 'LCP',
        priority: 'high',
        message: 'LCP is too slow. Optimize largest content element loading.',
      });
    }
    
    if (webVitals.CLS > 0.1) {
      recommendations.push({
        category: 'CLS',
        priority: 'medium',
        message: 'CLS score indicates layout shifts. Review image/ad loading.',
      });
    }
    
    if (webVitals.FCP > 1800) {
      recommendations.push({
        category: 'FCP',
        priority: 'medium',
        message: 'FCP is slow. Optimize critical resource loading.',
      });
    }
    
    return recommendations;
  }
  
  // 📊 Executar multiple audits
  async runMultipleAudits(urls, options = {}) {
    const results = [];
    
    for (const url of urls) {
      try {
        const result = await this.runAudit(url, options);
        results.push(result);
        
        // 💤 Pausa entre audits para não sobrecarregar
        await new Promise(resolve => setTimeout(resolve, 2000));
      } catch (error) {
        console.error(`Failed to audit ${url}:`, error);
        results.push({ url, error: error.message });
      }
    }
    
    return results;
  }
}

// 🚀 Exemplo de uso
async function runPerformanceAudit() {
  const monitor = new WebVitalsMonitor();
  const lighthouse = new LighthouseAutomation();
  
  const urls = [
    'http://localhost:3000',
    'http://localhost:3000/dashboard',
    'http://localhost:3000/users',
    'http://localhost:3000/analytics',
  ];
  
  console.log('🚀 Starting performance audit...');
  
  const results = await lighthouse.runMultipleAudits(urls);
  
  // 📊 Gerar relatório consolidado
  const consolidatedReport = {
    timestamp: new Date().toISOString(),
    summary: {
      totalPages: results.length,
      averagePerformanceScore: results.reduce((sum, r) => sum + (r.scores?.performanceScore || 0), 0) / results.length,
      totalOpportunities: results.reduce((sum, r) => sum + (r.opportunities?.length || 0), 0),
    },
    results,
  };
  
  fs.writeFileSync(
    `consolidated-performance-report-${Date.now()}.json`,
    JSON.stringify(consolidatedReport, null, 2)
  );
  
  console.log('✅ Performance audit completed!');
  return consolidatedReport;
}

export { WebVitalsMonitor, LighthouseAutomation, runPerformanceAudit };

🗄️ Database Performance Testing

performance-tests/database-performance.ts
// 📁 performance-tests/database-performance.ts
import { PrismaClient } from '@prisma/client';
import { performance } from 'perf_hooks';

// 🎯 Interface para métricas de database
interface DatabaseMetric {
  operation: string;
  duration: number;
  recordCount: number;
  queryType: 'select' | 'insert' | 'update' | 'delete' | 'aggregate';
  timestamp: number;
  query?: string;
}

// 📊 Classe para teste de performance de database
class DatabasePerformanceTester {
  private prisma: PrismaClient;
  private metrics: DatabaseMetric[] = [];
  
  constructor() {
    this.prisma = new PrismaClient({
      log: [
        { emit: 'event', level: 'query' },
        { emit: 'event', level: 'error' },
        { emit: 'event', level: 'info' },
        { emit: 'event', level: 'warn' },
      ],
    });
    
    this.setupQueryLogging();
  }
  
  // 📝 Configurar logging de queries
  private setupQueryLogging() {
    this.prisma.$on('query', (e) => {
      console.log(`🔍 Query: ${e.query}`);
      console.log(`📊 Duration: ${e.duration}ms`);
      console.log(`🎯 Params: ${e.params}`);
    });
  }
  
  // ⏱️ Medir performance de operação
  private async measureOperation<T>(
    operation: string,
    queryType: DatabaseMetric['queryType'],
    fn: () => Promise<T>
  ): Promise<{ result: T; metric: DatabaseMetric }> {
    const startTime = performance.now();
    
    const result = await fn();
    
    const endTime = performance.now();
    const duration = endTime - startTime;
    
    const recordCount = Array.isArray(result) ? result.length : 1;
    
    const metric: DatabaseMetric = {
      operation,
      duration,
      recordCount,
      queryType,
      timestamp: Date.now(),
    };
    
    this.metrics.push(metric);
    
    console.log(`📊 ${operation}: ${duration.toFixed(2)}ms (${recordCount} records)`);
    
    return { result, metric };
  }
  
  // 🧪 Teste de performance de SELECT
  async testSelectOperations() {
    console.log('\n🔍 Testing SELECT operations...');
    
    // 📊 Select simples
    await this.measureOperation(
      'simple-select-users',
      'select',
      () => this.prisma.user.findMany({ take: 100 })
    );
    
    // 🔍 Select com WHERE
    await this.measureOperation(
      'filtered-select-users',
      'select',
      () => this.prisma.user.findMany({
        where: {
          createdAt: {
            gte: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000), // 30 dias
          },
        },
        take: 100,
      })
    );
    
    // 🔗 Select com JOIN
    await this.measureOperation(
      'select-users-with-organization',
      'select',
      () => this.prisma.user.findMany({
        include: {
          organization: true,
          permissions: true,
        },
        take: 50,
      })
    );
    
    // 📄 Select com paginação
    for (let page = 0; page < 5; page++) {
      await this.measureOperation(
        `paginated-select-page-${page}`,
        'select',
        () => this.prisma.user.findMany({
          skip: page * 20,
          take: 20,
          orderBy: { createdAt: 'desc' },
        })
      );
    }
    
    // 🔍 Select com busca complexa
    await this.measureOperation(
      'complex-search-users',
      'select',
      () => this.prisma.user.findMany({
        where: {
          OR: [
            { name: { contains: 'test', mode: 'insensitive' } },
            { email: { contains: 'test', mode: 'insensitive' } },
          ],
          AND: [
            { organization: { isNot: null } },
            { createdAt: { gte: new Date('2024-01-01') } },
          ],
        },
        include: {
          organization: true,
        },
        take: 100,
      })
    );
  }
  
  // 📊 Teste de performance de AGGREGATE
  async testAggregateOperations() {
    console.log('\n📊 Testing AGGREGATE operations...');
    
    // 📈 Count básico
    await this.measureOperation(
      'count-users',
      'aggregate',
      () => this.prisma.user.count()
    );
    
    // 📊 Count com filtro
    await this.measureOperation(
      'count-active-users',
      'aggregate',
      () => this.prisma.user.count({
        where: {
          lastLoginAt: {
            gte: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), // 7 dias
          },
        },
      })
    );
    
    // 📈 Aggregate complexo
    await this.measureOperation(
      'aggregate-user-stats',
      'aggregate',
      () => this.prisma.user.aggregate({
        _count: { id: true },
        _min: { createdAt: true },
        _max: { createdAt: true },
        where: {
          organization: { isNot: null },
        },
      })
    );
    
    // 📊 Group by
    await this.measureOperation(
      'users-by-organization',
      'aggregate',
      () => this.prisma.user.groupBy({
        by: ['organizationId'],
        _count: { id: true },
        having: {
          id: { _count: { gt: 5 } },
        },
      })
    );
  }
  
  // ✏️ Teste de performance de INSERT
  async testInsertOperations() {
    console.log('\n✏️ Testing INSERT operations...');
    
    // 📝 Insert único
    await this.measureOperation(
      'single-insert-user',
      'insert',
      () => this.prisma.user.create({
        data: {
          email: `perftest-${Date.now()}@test.com`,
          name: 'Performance Test User',
          password: 'hashed-password',
        },
      })
    );
    
    // 📝 Bulk insert
    const bulkUsers = Array.from({ length: 100 }, (_, i) => ({
      email: `bulktest-${Date.now()}-${i}@test.com`,
      name: `Bulk Test User ${i}`,
      password: 'hashed-password',
    }));
    
    await this.measureOperation(
      'bulk-insert-users',
      'insert',
      () => this.prisma.user.createMany({
        data: bulkUsers,
        skipDuplicates: true,
      })
    );
    
    // 📝 Insert com relacionamento
    await this.measureOperation(
      'insert-user-with-organization',
      'insert',
      () => this.prisma.user.create({
        data: {
          email: `orgtest-${Date.now()}@test.com`,
          name: 'Organization Test User',
          password: 'hashed-password',
          organization: {
            create: {
              name: `Test Org ${Date.now()}`,
              plan: 'FREE',
            },
          },
        },
        include: {
          organization: true,
        },
      })
    );
  }
  
  // 🔄 Teste de performance de UPDATE
  async testUpdateOperations() {
    console.log('\n🔄 Testing UPDATE operations...');
    
    // 🔄 Update único
    const userToUpdate = await this.prisma.user.findFirst({
      where: { email: { startsWith: 'perftest-' } },
    });
    
    if (userToUpdate) {
      await this.measureOperation(
        'single-update-user',
        'update',
        () => this.prisma.user.update({
          where: { id: userToUpdate.id },
          data: {
            name: 'Updated Performance Test User',
            updatedAt: new Date(),
          },
        })
      );
    }
    
    // 🔄 Bulk update
    await this.measureOperation(
      'bulk-update-users',
      'update',
      () => this.prisma.user.updateMany({
        where: {
          email: { startsWith: 'bulktest-' },
        },
        data: {
          updatedAt: new Date(),
        },
      })
    );
  }
  
  // 🗑️ Teste de performance de DELETE
  async testDeleteOperations() {
    console.log('\n🗑️ Testing DELETE operations...');
    
    // 🗑️ Delete com filtro
    await this.measureOperation(
      'delete-test-users',
      'delete',
      () => this.prisma.user.deleteMany({
        where: {
          OR: [
            { email: { startsWith: 'perftest-' } },
            { email: { startsWith: 'bulktest-' } },
            { email: { startsWith: 'orgtest-' } },
          ],
        },
      })
    );
  }
  
  // 🔍 Teste de queries raw
  async testRawQueries() {
    console.log('\n🔍 Testing RAW queries...');
    
    // 📊 Query raw complexa
    await this.measureOperation(
      'raw-user-analytics',
      'select',
      () => this.prisma.$queryRaw`
        SELECT 
          DATE_TRUNC('day', "createdAt") as date,
          COUNT(*) as user_count,
          COUNT(DISTINCT "organizationId") as org_count
        FROM "User" 
        WHERE "createdAt" >= NOW() - INTERVAL '30 days'
        GROUP BY DATE_TRUNC('day', "createdAt")
        ORDER BY date DESC
      `
    );
    
    // 📈 Query com window functions
    await this.measureOperation(
      'raw-user-ranking',
      'select',
      () => this.prisma.$queryRaw`
        SELECT 
          u.*,
          ROW_NUMBER() OVER (PARTITION BY u."organizationId" ORDER BY u."createdAt" DESC) as rank_in_org
        FROM "User" u
        WHERE u."organizationId" IS NOT NULL
        LIMIT 100
      `
    );
  }
  
  // 📊 Executar todos os testes
  async runAllTests() {
    console.log('🚀 Starting database performance tests...');
    
    const startTime = performance.now();
    
    await this.testSelectOperations();
    await this.testAggregateOperations();
    await this.testInsertOperations();
    await this.testUpdateOperations();
    await this.testDeleteOperations();
    await this.testRawQueries();
    
    const totalTime = performance.now() - startTime;
    
    console.log(`\n✅ All database tests completed in ${totalTime.toFixed(2)}ms`);
    
    return this.generateReport();
  }
  
  // 📈 Gerar relatório de performance
  generateReport() {
    const report = {
      timestamp: new Date().toISOString(),
      totalOperations: this.metrics.length,
      totalDuration: this.metrics.reduce((sum, m) => sum + m.duration, 0),
      averageDuration: this.metrics.reduce((sum, m) => sum + m.duration, 0) / this.metrics.length,
      operationsByType: this.groupMetricsByType(),
      slowestOperations: this.findSlowestOperations(),
      fastestOperations: this.findFastestOperations(),
      recommendations: this.generateRecommendations(),
      allMetrics: this.metrics,
    };
    
    console.log('\n📊 Database Performance Report:');
    console.log(`Total Operations: ${report.totalOperations}`);
    console.log(`Total Duration: ${report.totalDuration.toFixed(2)}ms`);
    console.log(`Average Duration: ${report.averageDuration.toFixed(2)}ms`);
    
    console.log('\n🐌 Slowest Operations:');
    report.slowestOperations.slice(0, 5).forEach((op, i) => {
      console.log(`  ${i + 1}. ${op.operation}: ${op.duration.toFixed(2)}ms`);
    });
    
    console.log('\n⚡ Fastest Operations:');
    report.fastestOperations.slice(0, 5).forEach((op, i) => {
      console.log(`  ${i + 1}. ${op.operation}: ${op.duration.toFixed(2)}ms`);
    });
    
    return report;
  }
  
  private groupMetricsByType() {
    const grouped: Record<string, DatabaseMetric[]> = {};
    
    this.metrics.forEach(metric => {
      if (!grouped[metric.queryType]) {
        grouped[metric.queryType] = [];
      }
      grouped[metric.queryType].push(metric);
    });
    
    return Object.entries(grouped).reduce((acc, [type, metrics]) => {
      acc[type] = {
        count: metrics.length,
        totalDuration: metrics.reduce((sum, m) => sum + m.duration, 0),
        averageDuration: metrics.reduce((sum, m) => sum + m.duration, 0) / metrics.length,
        totalRecords: metrics.reduce((sum, m) => sum + m.recordCount, 0),
      };
      return acc;
    }, {} as Record<string, any>);
  }
  
  private findSlowestOperations() {
    return [...this.metrics]
      .sort((a, b) => b.duration - a.duration)
      .slice(0, 10);
  }
  
  private findFastestOperations() {
    return [...this.metrics]
      .sort((a, b) => a.duration - b.duration)
      .slice(0, 10);
  }
  
  private generateRecommendations() {
    const recommendations = [];
    
    const slowOperations = this.metrics.filter(m => m.duration > 1000);
    if (slowOperations.length > 0) {
      recommendations.push({
        category: 'Performance',
        priority: 'high',
        message: `${slowOperations.length} operations took more than 1 second. Consider adding indexes or optimizing queries.`,
      });
    }
    
    const heavySelects = this.metrics.filter(m => m.queryType === 'select' && m.recordCount > 1000);
    if (heavySelects.length > 0) {
      recommendations.push({
        category: 'Scalability',
        priority: 'medium',
        message: 'Large result sets detected. Consider implementing pagination.',
      });
    }
    
    return recommendations;
  }
  
  async cleanup() {
    await this.prisma.$disconnect();
  }
}

// 🚀 Executar testes
async function runDatabasePerformanceTests() {
  const tester = new DatabasePerformanceTester();
  
  try {
    const report = await tester.runAllTests();
    
    // 💾 Salvar relatório
    const fs = require('fs');
    const filename = `database-performance-report-${Date.now()}.json`;
    fs.writeFileSync(filename, JSON.stringify(report, null, 2));
    
    console.log(`\n💾 Database performance report saved: ${filename}`);
    
    return report;
  } finally {
    await tester.cleanup();
  }
}

export { DatabasePerformanceTester, runDatabasePerformanceTests };

🔄 CI/CD Performance Monitoring

.github/workflows/performance-monitoring.yml
# 📁 .github/workflows/performance-monitoring.yml
name: Performance Monitoring

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]
  schedule:
    # 🕒 Executar testes de performance diariamente às 2:00 AM
    - cron: '0 2 * * *'

jobs:
  performance-tests:
    runs-on: ubuntu-latest
    
    services:
      # 🗄️ PostgreSQL para testes
      postgres:
        image: postgres:15
        env:
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: trpc_performance_test
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432
      
      # 🔴 Redis para cache
      redis:
        image: redis:7
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 6379:6379
    
    steps:
      - name: 📥 Checkout code
        uses: actions/checkout@v4
      
      - name: 🔧 Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '18'
          cache: 'npm'
      
      - name: 📦 Install dependencies
        run: npm ci
      
      - name: 🏗️ Build application
        run: npm run build
        env:
          DATABASE_URL: postgresql://postgres:postgres@localhost:5432/trpc_performance_test
          REDIS_URL: redis://localhost:6379
      
      - name: 🚀 Start application
        run: |
          npm start &
          echo $! > app.pid
          # Aguardar aplicação iniciar
          timeout 60 bash -c 'until curl -f http://localhost:3000/api/health; do sleep 1; done'
        env:
          DATABASE_URL: postgresql://postgres:postgres@localhost:5432/trpc_performance_test
          REDIS_URL: redis://localhost:6379
          NODE_ENV: production
      
      - name: 🧪 Setup test data
        run: npm run db:seed
        env:
          DATABASE_URL: postgresql://postgres:postgres@localhost:5432/trpc_performance_test
      
      - name: ⚡ Install K6
        run: |
          sudo gpg -k
          sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
          echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
          sudo apt-get update
          sudo apt-get install k6
      
      - name: 🔥 Run load tests
        run: |
          k6 run --out json=load-test-results.json performance-tests/k6-trpc-test.js
        continue-on-error: true
      
      - name: 📊 Run memory profiling
        run: |
          node --expose-gc performance-tests/memory-profiling.js > memory-profile.log
        continue-on-error: true
      
      - name: 🗄️ Run database performance tests
        run: |
          npm run test:db-performance > db-performance.log
        env:
          DATABASE_URL: postgresql://postgres:postgres@localhost:5432/trpc_performance_test
        continue-on-error: true
      
      - name: 🌐 Install Lighthouse CI
        run: npm install -g @lhci/cli@0.12.x
      
      - name: 🔍 Run Lighthouse CI
        run: |
          lhci autorun
        env:
          LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
        continue-on-error: true
      
      - name: 📈 Process results
        run: |
          node performance-tests/process-ci-results.js
        continue-on-error: true
      
      - name: 📊 Upload performance results
        uses: actions/upload-artifact@v4
        with:
          name: performance-results
          path: |
            load-test-results.json
            memory-profile.log
            db-performance.log
            lighthouse-report-*.json
            performance-summary.json
      
      - name: 💬 Comment PR with results
        if: github.event_name == 'pull_request'
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            
            try {
              const summary = JSON.parse(fs.readFileSync('performance-summary.json', 'utf8'));
              
              const comment = `## 📊 Performance Test Results
              
              ### 🚀 Load Test Results
              - **Average Response Time**: ${summary.loadTest.avgResponseTime}ms
              - **95th Percentile**: ${summary.loadTest.p95}ms
              - **Error Rate**: ${summary.loadTest.errorRate}%
              - **Throughput**: ${summary.loadTest.throughput} req/s
              
              ### 🧠 Memory Analysis
              - **Peak Memory Usage**: ${summary.memory.peakMemory}MB
              - **Memory Leaks Detected**: ${summary.memory.leaksDetected ? '⚠️ Yes' : '✅ No'}
              - **GC Pressure**: ${summary.memory.gcPressure}
              
              ### 🗄️ Database Performance
              - **Average Query Time**: ${summary.database.avgQueryTime}ms
              - **Slowest Operation**: ${summary.database.slowestOperation}
              - **Total Queries**: ${summary.database.totalQueries}
              
              ### 🌐 Lighthouse Scores
              - **Performance**: ${summary.lighthouse.performance}/100
              - **Accessibility**: ${summary.lighthouse.accessibility}/100
              - **Best Practices**: ${summary.lighthouse.bestPractices}/100
              - **SEO**: ${summary.lighthouse.seo}/100
              
              ### 📈 Trends
              ${summary.trends.map(trend => `- ${trend}`).join('\n')}
              
              ### 💡 Recommendations
              ${summary.recommendations.map(rec => `- ⚠️ ${rec}`).join('\n')}
              `;
              
              github.rest.issues.createComment({
                issue_number: context.issue.number,
                owner: context.repo.owner,
                repo: context.repo.repo,
                body: comment
              });
            } catch (error) {
              console.log('Could not create performance comment:', error);
            }
      
      - name: 🛑 Stop application
        run: |
          if [ -f app.pid ]; then
            kill $(cat app.pid) || true
            rm app.pid
          fi
      
      - name: 📊 Send metrics to monitoring
        if: github.ref == 'refs/heads/main'
        run: |
          # Enviar métricas para Datadog, New Relic, etc.
          curl -X POST "https://api.datadoghq.com/api/v1/series" \
            -H "Content-Type: application/json" \
            -H "DD-API-KEY: ${{ secrets.DATADOG_API_KEY }}" \
            -d @performance-metrics.json
        continue-on-error: true

# 📁 performance-tests/process-ci-results.js
const fs = require('fs');

function processResults() {
  const results = {
    timestamp: new Date().toISOString(),
    loadTest: processLoadTestResults(),
    memory: processMemoryResults(),
    database: processDatabaseResults(),
    lighthouse: processLighthouseResults(),
    trends: [],
    recommendations: [],
  };
  
  // 📊 Analisar tendências
  results.trends = analyzeTrends(results);
  
  // 💡 Gerar recomendações
  results.recommendations = generateRecommendations(results);
  
  // 💾 Salvar summary
  fs.writeFileSync('performance-summary.json', JSON.stringify(results, null, 2));
  
  // 📈 Preparar métricas para monitoramento
  const metrics = prepareMetricsForMonitoring(results);
  fs.writeFileSync('performance-metrics.json', JSON.stringify(metrics, null, 2));
  
  console.log('📊 Performance results processed successfully');
  return results;
}

function processLoadTestResults() {
  try {
    const rawData = fs.readFileSync('load-test-results.json', 'utf8');
    const data = rawData.split('\n').filter(line => line.trim()).map(line => JSON.parse(line));
    
    const httpReqs = data.filter(d => d.type === 'Point' && d.metric === 'http_reqs');
    const httpDuration = data.filter(d => d.type === 'Point' && d.metric === 'http_req_duration');
    const httpFailures = data.filter(d => d.type === 'Point' && d.metric === 'http_req_failed');
    
    return {
      totalRequests: httpReqs.length,
      avgResponseTime: httpDuration.reduce((sum, d) => sum + d.data.value, 0) / httpDuration.length,
      p95: calculatePercentile(httpDuration.map(d => d.data.value), 95),
      errorRate: (httpFailures.filter(d => d.data.value > 0).length / httpFailures.length) * 100,
      throughput: httpReqs.length / 60, // assumindo 1 minuto de teste
    };
  } catch (error) {
    console.warn('Could not process load test results:', error);
    return { error: 'Failed to process load test results' };
  }
}

function processMemoryResults() {
  try {
    const logData = fs.readFileSync('memory-profile.log', 'utf8');
    const lines = logData.split('\n');
    
    // Extrair métricas do log
    const memoryPattern = /Peak Memory: ([0-9.]+)MB/;
    const gcPattern = /Total GC Time: ([0-9.]+)ms/;
    
    const memoryMatch = logData.match(memoryPattern);
    const gcMatch = logData.match(gcPattern);
    
    return {
      peakMemory: memoryMatch ? parseFloat(memoryMatch[1]) : 0,
      totalGCTime: gcMatch ? parseFloat(gcMatch[1]) : 0,
      leaksDetected: logData.includes('Memory leak detected'),
      gcPressure: gcMatch && parseFloat(gcMatch[1]) > 1000 ? 'High' : 'Normal',
    };
  } catch (error) {
    console.warn('Could not process memory results:', error);
    return { error: 'Failed to process memory results' };
  }
}

function processDatabaseResults() {
  try {
    const logData = fs.readFileSync('db-performance.log', 'utf8');
    
    // Extrair métricas do log
    const avgTimePattern = /Average Duration: ([0-9.]+)ms/;
    const totalOpsPattern = /Total Operations: ([0-9]+)/;
    
    const avgMatch = logData.match(avgTimePattern);
    const totalMatch = logData.match(totalOpsPattern);
    
    return {
      avgQueryTime: avgMatch ? parseFloat(avgMatch[1]) : 0,
      totalQueries: totalMatch ? parseInt(totalMatch[1]) : 0,
      slowestOperation: 'getUserList', // Seria extraído do log real
    };
  } catch (error) {
    console.warn('Could not process database results:', error);
    return { error: 'Failed to process database results' };
  }
}

function processLighthouseResults() {
  try {
    const files = fs.readdirSync('.').filter(f => f.startsWith('lighthouse-report-'));
    if (files.length === 0) return { error: 'No Lighthouse reports found' };
    
    const report = JSON.parse(fs.readFileSync(files[0], 'utf8'));
    
    return {
      performance: report.scores?.performanceScore || 0,
      accessibility: report.scores?.accessibilityScore || 0,
      bestPractices: report.scores?.bestPracticesScore || 0,
      seo: report.scores?.seoScore || 0,
      lcp: report.webVitals?.LCP || 0,
      fid: report.webVitals?.FID || 0,
      cls: report.webVitals?.CLS || 0,
    };
  } catch (error) {
    console.warn('Could not process Lighthouse results:', error);
    return { error: 'Failed to process Lighthouse results' };
  }
}

function analyzeTrends(results) {
  const trends = [];
  
  // Comparar com resultados anteriores (seria implementado com armazenamento histórico)
  if (results.loadTest.avgResponseTime > 500) {
    trends.push('📈 Response time trending upward (>500ms average)');
  }
  
  if (results.memory.peakMemory > 512) {
    trends.push('🧠 Memory usage increasing (>512MB peak)');
  }
  
  if (results.lighthouse.performance < 80) {
    trends.push('🌐 Lighthouse performance score declining (<80)');
  }
  
  return trends;
}

function generateRecommendations(results) {
  const recommendations = [];
  
  if (results.loadTest.avgResponseTime > 300) {
    recommendations.push('Optimize API response times (currently >300ms)');
  }
  
  if (results.loadTest.errorRate > 1) {
    recommendations.push('Investigate and fix errors (>1% error rate)');
  }
  
  if (results.memory.gcPressure === 'High') {
    recommendations.push('Review memory usage and potential leaks');
  }
  
  if (results.database.avgQueryTime > 100) {
    recommendations.push('Optimize database queries (>100ms average)');
  }
  
  if (results.lighthouse.performance < 90) {
    recommendations.push('Improve Core Web Vitals and frontend performance');
  }
  
  return recommendations;
}

function prepareMetricsForMonitoring(results) {
  const now = Math.floor(Date.now() / 1000);
  
  return {
    series: [
      {
        metric: 'trpc.performance.response_time',
        points: [[now, results.loadTest.avgResponseTime]],
        tags: ['env:ci', 'service:trpc'],
      },
      {
        metric: 'trpc.performance.error_rate',
        points: [[now, results.loadTest.errorRate]],
        tags: ['env:ci', 'service:trpc'],
      },
      {
        metric: 'trpc.performance.memory_peak',
        points: [[now, results.memory.peakMemory]],
        tags: ['env:ci', 'service:trpc'],
      },
      {
        metric: 'trpc.performance.lighthouse_score',
        points: [[now, results.lighthouse.performance]],
        tags: ['env:ci', 'service:trpc'],
      },
    ],
  };
}

function calculatePercentile(values, percentile) {
  const sorted = values.sort((a, b) => a - b);
  const index = Math.ceil((percentile / 100) * sorted.length) - 1;
  return sorted[index];
}

// 🚀 Executar se for script principal
if (require.main === module) {
  processResults();
}

module.exports = { processResults };

💡 Melhores Práticas para Performance Testing

Testes Realistas:Use dados e cenários que representem o uso real em produção.

Monitoramento Contínuo:Integre testes de performance no CI/CD para detectar regressões cedo.

Métricas Específicas:Defina SLOs claros para cada endpoint baseado nos requisitos de negócio.

Ambiente Isolado:Execute testes em ambiente separado que simule produção.