Domine performance testing para tRPC: load testing avançado, stress testing, memory profiling, Core Web Vitals e otimização completa para SaaS enterprise-grade.
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.
// 📁 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
};
// 📁 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);
}
// 📁 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 };
// 📁 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 };
// 📁 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 };
# 📁 .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 };
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.