Analiza wydajności
Wydajność to różnica między oprogramowaniem, które zachwyca, a oprogramowaniem, które frustruje. Czy optymalizujesz czasy odpowiedzi, redukujesz zużycie pamięci, czy skalujesz dla milionów użytkowników, Claude Code przekształca optymalizację wydajności z zgadywania w inżynierię opartą na danych. Ta lekcja bada, jak wykorzystać pomoc sztucznej inteligencji do kompleksowej analizy i optymalizacji wydajności.
Rewolucja wydajności
Dział zatytułowany „Rewolucja wydajności”Scenariusz: Twoja aplikacja była szybka z 100 użytkownikami. Teraz z 10 000 użytkowników raczkuje. Zapytania do bazy danych się przedawniają, API odpowiadają wolno, a użytkownicy narzekają. Tradycyjne podejście: rzuć więcej serwerów i miej nadzieję. Z Claude Code: systematyczna analiza, która znajduje i naprawia prawdziwe wąskie gardła.
Tradycyjna a optymalizacja wydajności z pomocą AI
Dział zatytułowany „Tradycyjna a optymalizacja wydajności z pomocą AI”Tydzień 1: Zaczynają się skargi- "Aplikacja jest wolna"- Dodaj więcej serwerów- Nadal wolna
Tydzień 2: Losowa optymalizacja- Buforuj wszystko- Dodaj indeksy wszędzie- Zepsuć coś innego
Tydzień 3: Próba profilowania- Złożona konfiguracja profilera- Przytłaczające dane- Niepewność co naprawić
Tydzień 4: Konsultanci- Kosztowna analiza- Ogólne rekomendacje- Marginalne poprawy
Dzień 1: Systematyczna analiza> Profiluj zachowanie aplikacji> Zidentyfikuj wąskie gardła> Zmierz metryki bazowe
Dzień 2: Celowana optymalizacja> Napraw zidentyfikowane wąskie gardła> Optymalizuj krytyczne ścieżki> Zweryfikuj poprawy
Dzień 3: Testowanie skali> Test obciążeniowy optymalizacji> Dostrajaj na podstawie danych> Wdrażaj z pewnością
Ciągle: Ciągła optymalizacja> Monitoruj wydajność> Proaktywna optymalizacja> Zapobiegaj degradacji
Profilowanie wydajności
Dział zatytułowany „Profilowanie wydajności”Kompleksowe profilowanie aplikacji
Dział zatytułowany „Kompleksowe profilowanie aplikacji”> Skonfiguruj kompleksowe profilowanie wydajności dla naszej aplikacji:> - Profilowanie CPU> - Profilowanie pamięci> - Analiza zapytań do bazy danych> - Pomiar czasu żądań sieciowych> - Wydajność renderowania> Utwórz dashboard wizualizacji
Claude tworzy kompletną konfigurację profilowania:
-
Monitorowanie wydajności aplikacji
performance-monitor.js const { performance, PerformanceObserver } = require('perf_hooks');const v8 = require('v8');const os = require('os');class PerformanceMonitor {constructor() {this.metrics = {requests: new Map(),database: new Map(),cache: new Map(),cpu: [],memory: []};this.startMonitoring();}startMonitoring() {// Pomiar czasu żądańthis.requestObserver = new PerformanceObserver((list) => {for (const entry of list.getEntries()) {if (entry.name.startsWith('request-')) {this.recordRequest(entry);}}});this.requestObserver.observe({ entryTypes: ['measure'] });// Metryki systemowethis.systemInterval = setInterval(() => {this.recordSystemMetrics();}, 1000);// Statystyki sterty V8this.heapInterval = setInterval(() => {this.recordHeapStatistics();}, 5000);}recordRequest(entry) {const route = entry.name.replace('request-', '');if (!this.metrics.requests.has(route)) {this.metrics.requests.set(route, {count: 0,totalTime: 0,minTime: Infinity,maxTime: 0,p50: 0,p95: 0,p99: 0,times: []});}const stats = this.metrics.requests.get(route);stats.count++;stats.totalTime += entry.duration;stats.minTime = Math.min(stats.minTime, entry.duration);stats.maxTime = Math.max(stats.maxTime, entry.duration);stats.times.push(entry.duration);// Oblicz percentyleif (stats.times.length > 100) {const sorted = [...stats.times].sort((a, b) => a - b);stats.p50 = sorted[Math.floor(sorted.length * 0.5)];stats.p95 = sorted[Math.floor(sorted.length * 0.95)];stats.p99 = sorted[Math.floor(sorted.length * 0.99)];// Zachowaj tylko ostatnie 1000 próbekstats.times = stats.times.slice(-1000);}}recordSystemMetrics() {const cpus = os.cpus();const totalIdle = cpus.reduce((acc, cpu) => acc + cpu.times.idle, 0);const totalTick = cpus.reduce((acc, cpu) =>acc + Object.values(cpu.times).reduce((a, b) => a + b), 0);const cpuUsage = 100 - ~~(100 * totalIdle / totalTick);this.metrics.cpu.push({timestamp: Date.now(),usage: cpuUsage,loadAvg: os.loadavg()});// Wykorzystanie pamięciconst memUsage = process.memoryUsage();this.metrics.memory.push({timestamp: Date.now(),rss: memUsage.rss,heapTotal: memUsage.heapTotal,heapUsed: memUsage.heapUsed,external: memUsage.external,arrayBuffers: memUsage.arrayBuffers});// Zachowaj tylko dane z ostatniej godzinyconst hourAgo = Date.now() - 3600000;this.metrics.cpu = this.metrics.cpu.filter(m => m.timestamp > hourAgo);this.metrics.memory = this.metrics.memory.filter(m => m.timestamp > hourAgo);}recordHeapStatistics() {const heapStats = v8.getHeapStatistics();const heapSpaces = v8.getHeapSpaceStatistics();// Wykryj wycieki pamięciif (heapStats.used_heap_size > heapStats.heap_size_limit * 0.9) {console.warn('OSTRZEŻENIE: Wykorzystanie sterty powyżej 90%');this.takeHeapSnapshot();}}async takeHeapSnapshot() {const { writeHeapSnapshot } = require('v8');const filename = `heap-${Date.now()}.heapsnapshot`;writeHeapSnapshot(filename);console.log(`Zrzut sterty zapisany do ${filename}`);}getReport() {const report = {timestamp: new Date().toISOString(),requests: {},system: {cpu: this.calculateStats(this.metrics.cpu.map(m => m.usage)),memory: this.calculateStats(this.metrics.memory.map(m => m.heapUsed))}};// Konwertuj metryki żądań do formatu raportufor (const [route, stats] of this.metrics.requests) {report.requests[route] = {count: stats.count,avgTime: stats.totalTime / stats.count,minTime: stats.minTime,maxTime: stats.maxTime,p50: stats.p50,p95: stats.p95,p99: stats.p99};}return report;}calculateStats(values) {if (values.length === 0) return {};const sorted = [...values].sort((a, b) => a - b);return {min: sorted[0],max: sorted[sorted.length - 1],avg: values.reduce((a, b) => a + b) / values.length,p50: sorted[Math.floor(sorted.length * 0.5)],p95: sorted[Math.floor(sorted.length * 0.95)],p99: sorted[Math.floor(sorted.length * 0.99)]};}}// Middleware Express do pomiaru czasu żądańfunction performanceMiddleware(monitor) {return (req, res, next) => {const start = performance.now();const route = req.route?.path || req.path;res.on('finish', () => {const duration = performance.now() - start;performance.measure(`request-${route}`, {start,duration});});next();};}module.exports = { PerformanceMonitor, performanceMiddleware }; -
Profilowanie zapytań do bazy danych
db-profiler.js class DatabaseProfiler {constructor(db) {this.db = db;this.queries = new Map();this.slowQueryThreshold = 100; // msthis.wrapDatabase();}wrapDatabase() {const originalQuery = this.db.query.bind(this.db);this.db.query = async (sql, params) => {const start = performance.now();const stack = new Error().stack;try {const result = await originalQuery(sql, params);const duration = performance.now() - start;this.recordQuery(sql, duration, true, stack);if (duration > this.slowQueryThreshold) {this.handleSlowQuery(sql, duration, params);}return result;} catch (error) {const duration = performance.now() - start;this.recordQuery(sql, duration, false, stack);throw error;}};}recordQuery(sql, duration, success, stack) {const queryKey = this.normalizeQuery(sql);if (!this.queries.has(queryKey)) {this.queries.set(queryKey, {query: queryKey,count: 0,totalTime: 0,minTime: Infinity,maxTime: 0,failures: 0,locations: new Set()});}const stats = this.queries.get(queryKey);stats.count++;stats.totalTime += duration;stats.minTime = Math.min(stats.minTime, duration);stats.maxTime = Math.max(stats.maxTime, duration);if (!success) stats.failures++;// Wyodrębnij lokalizację wywołaniaconst caller = this.extractCaller(stack);if (caller) stats.locations.add(caller);}normalizeQuery(sql) {// Usuń konkretne wartości, aby pogrupować podobne zapytaniareturn sql.replace(/\b\d+\b/g, '?').replace(/'[^']*'/g, '?').replace(/\s+/g, ' ').trim();}extractCaller(stack) {const lines = stack.split('\n');// Znajdź pierwszą linię nienależącą do frameworkafor (const line of lines.slice(2)) {if (!line.includes('node_modules') && line.includes('.js')) {const match = line.match(/at .* \((.*:\d+:\d+)\)/);return match ? match[1] : null;}}return null;}async handleSlowQuery(sql, duration, params) {console.warn(`Wykryto wolne zapytanie (${duration.toFixed(2)}ms):`);console.warn(sql);// Uzyskaj plan wykonania zapytaniatry {const explainResult = await this.db.query(`EXPLAIN ANALYZE ${sql}`, params);console.warn('Plan wykonania:', explainResult.rows);// Sprawdź brakujące indeksyconst indexSuggestions = this.suggestIndexes(explainResult.rows);if (indexSuggestions.length > 0) {console.warn('Sugerowane indeksy:', indexSuggestions);}} catch (error) {// EXPLAIN może nie działać dla niektórych zapytań}}suggestIndexes(explainRows) {const suggestions = [];for (const row of explainRows) {const plan = row['QUERY PLAN'] || row.plan || '';// Szukaj skanów sekwencyjnych na dużych tabelachif (plan.includes('Seq Scan') && plan.includes('rows=')) {const rowMatch = plan.match(/rows=(\d+)/);if (rowMatch && parseInt(rowMatch[1]) > 1000) {const tableMatch = plan.match(/on (\w+)/);if (tableMatch) {suggestions.push(`Rozważ indeks na ${tableMatch[1]}`);}}}// Szukaj kosztownych sortowańif (plan.includes('Sort') && plan.includes('Sort Method: external')) {suggestions.push('Rozważ indeks dla kolumn ORDER BY');}}return [...new Set(suggestions)];}getSlowQueries(limit = 10) {const queries = Array.from(this.queries.values()).filter(q => q.maxTime > this.slowQueryThreshold).sort((a, b) => b.totalTime - a.totalTime).slice(0, limit);return queries.map(q => ({query: q.query,count: q.count,avgTime: q.totalTime / q.count,maxTime: q.maxTime,totalTime: q.totalTime,locations: Array.from(q.locations)}));}}module.exports = DatabaseProfiler; -
Wykrywanie wycieków pamięci
memory-leak-detector.js const { writeHeapSnapshot } = require('v8');const fs = require('fs').promises;class MemoryLeakDetector {constructor(options = {}) {this.threshold = options.threshold || 100 * 1024 * 1024; // 100MBthis.checkInterval = options.checkInterval || 60000; // 1 minutathis.samples = [];this.leaks = [];this.startMonitoring();}startMonitoring() {this.interval = setInterval(() => {this.checkMemory();}, this.checkInterval);// Ustaw bazę początkowąthis.takeBaseline();}async takeBaseline() {if (global.gc) {global.gc();}await new Promise(resolve => setTimeout(resolve, 1000));const baseline = process.memoryUsage();this.samples.push({timestamp: Date.now(),...baseline});}checkMemory() {const current = process.memoryUsage();const sample = {timestamp: Date.now(),...current};this.samples.push(sample);// Zachowaj tylko próbki z ostatniej godzinyconst hourAgo = Date.now() - 3600000;this.samples = this.samples.filter(s => s.timestamp > hourAgo);// Analizuj w poszukiwaniu wyciekówconst leak = this.detectLeak();if (leak) {this.handleLeak(leak);}}detectLeak() {if (this.samples.length < 5) return null;// Oblicz tempo wzrostuconst recent = this.samples.slice(-5);const old = this.samples.slice(0, 5);const recentAvg = recent.reduce((sum, s) => sum + s.heapUsed, 0) / recent.length;const oldAvg = old.reduce((sum, s) => sum + s.heapUsed, 0) / old.length;const growth = recentAvg - oldAvg;const growthRate = growth / oldAvg;// Wykryj ciągły wzrostif (growth > this.threshold && growthRate > 0.2) {return {growth,growthRate,currentHeap: recentAvg,samples: this.samples.slice(-10)};}return null;}async handleLeak(leak) {console.error('Wykryto wyciek pamięci!', {growth: `${(leak.growth / 1024 / 1024).toFixed(2)} MB`,rate: `${(leak.growthRate * 100).toFixed(2)}%`,currentHeap: `${(leak.currentHeap / 1024 / 1024).toFixed(2)} MB`});this.leaks.push({timestamp: Date.now(),...leak});// Wykonaj zrzut stertyawait this.captureHeapSnapshot();// Analizuj źródła alokacjiconst sources = await this.findAllocationSources();if (sources.length > 0) {console.error('Potencjalne źródła wycieków:', sources);}}async captureHeapSnapshot() {const filename = `leak-${Date.now()}.heapsnapshot`;writeHeapSnapshot(filename);console.log(`Zrzut sterty zapisany: ${filename}`);// Porównaj z poprzednim zrzutem jeśli istniejeif (this.previousSnapshot) {await this.compareSnapshots(this.previousSnapshot, filename);}this.previousSnapshot = filename;}async findAllocationSources() {// Sprawdź typowe źródła wyciekówconst sources = [];// Nasłuchiwacze zdarzeńif (process._events) {const eventCounts = {};for (const event in process._events) {const listeners = process._events[event];const count = Array.isArray(listeners) ? listeners.length : 1;if (count > 10) {sources.push(`Wysoka liczba nasłuchiwaczy dla '${event}': ${count}`);}}}// Zmienne globalneconst globalVars = Object.keys(global).length;if (globalVars > 100) {sources.push(`Wysoka liczba zmiennych globalnych: ${globalVars}`);}// Timeryconst activeTimers = process._getActiveHandles? process._getActiveHandles().filter(h => h.constructor.name === 'Timer').length: 0;if (activeTimers > 50) {sources.push(`Wysoka liczba aktywnych timerów: ${activeTimers}`);}return sources;}}module.exports = MemoryLeakDetector;
Profilowanie wydajności frontendu
Dział zatytułowany „Profilowanie wydajności frontendu”> Utwórz profilowanie wydajności frontendu:> - Czasy renderowania komponentów React> - Analiza rozmiaru bundla> - Waterfall sieci> - Monitorowanie Core Web Vitals
class FrontendProfiler { constructor() { this.metrics = { components: new Map(), vitals: [], resources: [] };
this.initializeObservers(); }
initializeObservers() { // Performance Observer dla różnych metryk if ('PerformanceObserver' in window) { // Largest Contentful Paint new PerformanceObserver((entryList) => { for (const entry of entryList.getEntries()) { this.metrics.vitals.push({ name: 'LCP', value: entry.renderTime || entry.loadTime, timestamp: Date.now() }); } }).observe({ entryTypes: ['largest-contentful-paint'] });
// First Input Delay new PerformanceObserver((entryList) => { for (const entry of entryList.getEntries()) { this.metrics.vitals.push({ name: 'FID', value: entry.processingStart - entry.startTime, timestamp: Date.now() }); } }).observe({ entryTypes: ['first-input'] });
// Cumulative Layout Shift let clsValue = 0; new PerformanceObserver((entryList) => { for (const entry of entryList.getEntries()) { if (!entry.hadRecentInput) { clsValue += entry.value; } } this.metrics.vitals.push({ name: 'CLS', value: clsValue, timestamp: Date.now() }); }).observe({ entryTypes: ['layout-shift'] }); } }
// Profilowanie komponentów React profileComponent(Component) { const WrappedComponent = React.forwardRef((props, ref) => { const renderStart = performance.now();
React.useEffect(() => { const renderEnd = performance.now(); const componentName = Component.displayName || Component.name || 'Unknown';
if (!this.metrics.components.has(componentName)) { this.metrics.components.set(componentName, { renders: 0, totalTime: 0, avgTime: 0, maxTime: 0 }); }
const stats = this.metrics.components.get(componentName); stats.renders++; stats.totalTime += renderEnd - renderStart; stats.avgTime = stats.totalTime / stats.renders; stats.maxTime = Math.max(stats.maxTime, renderEnd - renderStart); });
return <Component ref={ref} {...props} />; });
WrappedComponent.displayName = `Profiled(${Component.displayName || Component.name})`; return WrappedComponent; }
// Analiza rozmiaru bundla async analyzeBundleSize() { const resources = performance.getEntriesByType('resource'); const scripts = resources.filter(r => r.name.endsWith('.js'));
const analysis = { totalSize: 0, scripts: [], largestScripts: [] };
for (const script of scripts) { const size = script.transferSize || script.encodedBodySize || 0; analysis.totalSize += size;
const scriptInfo = { name: script.name.split('/').pop(), size, duration: script.duration, compressed: script.transferSize < script.decodedBodySize };
analysis.scripts.push(scriptInfo); }
analysis.largestScripts = analysis.scripts .sort((a, b) => b.size - a.size) .slice(0, 5);
return analysis; }
// Waterfall sieci getNetworkWaterfall() { const resources = performance.getEntriesByType('resource');
return resources .map(resource => ({ name: resource.name, type: this.getResourceType(resource.name), startTime: resource.startTime, duration: resource.duration, size: resource.transferSize || 0, timeline: { dns: resource.domainLookupEnd - resource.domainLookupStart, tcp: resource.connectEnd - resource.connectStart, ssl: resource.secureConnectionStart > 0 ? resource.connectEnd - resource.secureConnectionStart : 0, ttfb: resource.responseStart - resource.requestStart, download: resource.responseEnd - resource.responseStart } })) .sort((a, b) => a.startTime - b.startTime); }
getResourceType(url) { if (url.match(/\.(js|mjs)$/)) return 'script'; if (url.match(/\.css$/)) return 'stylesheet'; if (url.match(/\.(jpg|jpeg|png|gif|webp|svg)$/)) return 'image'; if (url.match(/\.(woff|woff2|ttf|eot)$/)) return 'font'; if (url.match(/\.json$/)) return 'json'; return 'other'; }
// Generuj raport wydajności generateReport() { return { timestamp: Date.now(), vitals: this.getWebVitals(), components: this.getSlowComponents(), bundle: this.analyzeBundleSize(), network: this.getNetworkWaterfall() }; }
getWebVitals() { const vitals = {}; ['LCP', 'FID', 'CLS'].forEach(metric => { const values = this.metrics.vitals .filter(v => v.name === metric) .map(v => v.value);
if (values.length > 0) { vitals[metric] = { value: values[values.length - 1], rating: this.getVitalRating(metric, values[values.length - 1]) }; } });
return vitals; }
getVitalRating(metric, value) { const thresholds = { LCP: { good: 2500, poor: 4000 }, FID: { good: 100, poor: 300 }, CLS: { good: 0.1, poor: 0.25 } };
const threshold = thresholds[metric]; if (value <= threshold.good) return 'good'; if (value <= threshold.poor) return 'needs-improvement'; return 'poor'; }
getSlowComponents() { return Array.from(this.metrics.components.entries()) .map(([name, stats]) => ({ name, ...stats })) .filter(c => c.avgTime > 16) // Wolniejsze niż 60fps .sort((a, b) => b.avgTime - a.avgTime) .slice(0, 10); }}
Wykrywanie wąskich gardeł
Dział zatytułowany „Wykrywanie wąskich gardeł”Automatyczna analiza wąskich gardeł
Dział zatytułowany „Automatyczna analiza wąskich gardeł”> Przeanalizuj wąskie gardła wydajności naszej aplikacji:> - Zidentyfikuj wolne zapytania do bazy danych> - Znajdź operacje intensywnie wykorzystujące CPU> - Wykryj wycieki pamięci> - Przeanalizuj opóźnienia sieciowe> - Profiluj wydajność renderowania> Dostarcz konkretne rekomendacje optymalizacji
class BottleneckAnalyzer { constructor(performanceData) { this.data = performanceData; this.bottlenecks = []; }
async analyze() { // Analizuj różne aspekty await Promise.all([ this.analyzeDatabaseQueries(), this.analyzeCPUUsage(), this.analyzeMemoryUsage(), this.analyzeNetworkLatency(), this.analyzeRenderingPerformance() ]);
// Priorytetyzuj wąskie gardła według wpływu this.bottlenecks.sort((a, b) => b.impact - a.impact);
return this.generateReport(); }
async analyzeDatabaseQueries() { const slowQueries = this.data.database.queries .filter(q => q.avgTime > 100) // > 100ms średnio .sort((a, b) => b.totalTime - a.totalTime);
for (const query of slowQueries.slice(0, 5)) { const impact = (query.totalTime / this.data.totalTime) * 100;
this.bottlenecks.push({ type: 'database', severity: impact > 20 ? 'critical' : impact > 10 ? 'high' : 'medium', impact, details: { query: query.query, avgTime: query.avgTime, count: query.count, totalTime: query.totalTime }, recommendations: await this.getDatabaseRecommendations(query) }); } }
async getDatabaseRecommendations(query) { const recommendations = [];
// Sprawdź brakujące indeksy if (query.query.includes('WHERE') && query.avgTime > 200) { recommendations.push({ title: 'Dodaj indeks', description: 'Rozważ dodanie indeksu na kolumnach klauzuli WHERE', example: this.generateIndexSuggestion(query.query) }); }
// Sprawdź zapytania N+1 if (query.count > 100 && query.query.includes('SELECT')) { recommendations.push({ title: 'Zapytania wsadowe', description: 'To zapytanie jest wykonywane wiele razy. Rozważ wsadowe przetwarzanie lub użycie JOIN', example: 'Użyj pojedynczego zapytania z JOIN lub klauzulą IN zamiast wielu zapytań' }); }
// Sprawdź pełne skanowanie tabeli if (query.plan && query.plan.includes('Seq Scan')) { recommendations.push({ title: 'Unikaj pełnego skanowania tabeli', description: 'Zapytanie wykonuje skanowanie sekwencyjne', example: 'Dodaj odpowiednie indeksy lub ogranicz zestaw wyników' }); }
return recommendations; }
generateIndexSuggestion(query) { // Wyodrębnij nazwy tabel i kolumn z klauzuli WHERE const whereMatch = query.match(/FROM\s+(\w+).*WHERE\s+(\w+)/i); if (whereMatch) { return `CREATE INDEX idx_${whereMatch[1]}_${whereMatch[2]} ON ${whereMatch[1]}(${whereMatch[2]});`; } return 'CREATE INDEX idx_table_column ON table(column);'; }
async analyzeCPUUsage() { const cpuSpikes = this.data.cpu.samples.filter(s => s.usage > 80);
if (cpuSpikes.length > 0) { const avgHighCPU = cpuSpikes.reduce((sum, s) => sum + s.usage, 0) / cpuSpikes.length;
this.bottlenecks.push({ type: 'cpu', severity: avgHighCPU > 90 ? 'critical' : 'high', impact: cpuSpikes.length / this.data.cpu.samples.length * 100, details: { avgHighCPU, spikeCount: cpuSpikes.length, duration: cpuSpikes.length * this.data.cpu.sampleInterval }, recommendations: [ { title: 'Profiluj wykorzystanie CPU', description: 'Użyj profilera CPU do identyfikacji gorących funkcji', example: 'node --prof app.js && node --prof-process isolate-*.log' }, { title: 'Optymalizuj algorytmy', description: 'Przejrzyj złożoność algorytmiczną w gorących ścieżkach', example: 'Zastąp operacje O(n²) alternatywami O(n log n)' }, { title: 'Dodaj buforowanie', description: 'Buforuj wyniki obliczeń aby uniknąć powtarzających się kalkulacji', example: 'Zaimplementuj memoizację dla kosztownych funkcji' } ] }); } }
async analyzeMemoryUsage() { const memoryGrowth = this.calculateMemoryGrowth();
if (memoryGrowth.rate > 0.1) { // 10% wzrost this.bottlenecks.push({ type: 'memory', severity: memoryGrowth.rate > 0.5 ? 'critical' : 'high', impact: memoryGrowth.rate * 100, details: { growthRate: memoryGrowth.rate, totalGrowth: memoryGrowth.total, suspectedLeaks: memoryGrowth.leaks }, recommendations: [ { title: 'Napraw wycieki pamięci', description: 'Usuń nasłuchiwacze zdarzeń i wyczyść referencje', example: `// Usuń nasłuchiwacze po zakończeniuemitter.removeListener('event', handler);
// Wyczyść timeryclearInterval(intervalId);
// Nullify referencjelargeObject = null;` }, { title: 'Użyj WeakMap/WeakSet', description: 'Użyj słabych referencji dla pamięci podręcznych', example: 'const cache = new WeakMap(); // Pozwala na garbage collection' }, { title: 'Strumieniuj duże dane', description: 'Przetwarzaj duże zestawy danych w kawałkach', example: 'Użyj strumieni zamiast ładowania całych plików do pamięci' } ] }); } }
calculateMemoryGrowth() { const samples = this.data.memory.samples; if (samples.length < 2) return { rate: 0, total: 0, leaks: [] };
const first = samples[0]; const last = samples[samples.length - 1];
const totalGrowth = last.heapUsed - first.heapUsed; const rate = totalGrowth / first.heapUsed;
// Wykryj potencjalne źródła wycieków const leaks = []; if (this.data.memory.eventListeners > 100) { leaks.push('Nadmierna liczba nasłuchiwaczy zdarzeń'); } if (this.data.memory.timers > 50) { leaks.push('Wiele aktywnych timerów'); }
return { rate, total: totalGrowth, leaks }; }
async analyzeNetworkLatency() { const slowRequests = this.data.network.requests .filter(r => r.duration > 1000) // > 1 sekunda .sort((a, b) => b.duration - a.duration);
if (slowRequests.length > 0) { const avgLatency = slowRequests.reduce((sum, r) => sum + r.duration, 0) / slowRequests.length;
this.bottlenecks.push({ type: 'network', severity: avgLatency > 3000 ? 'critical' : 'high', impact: slowRequests.length / this.data.network.requests.length * 100, details: { slowRequests: slowRequests.slice(0, 5), avgLatency }, recommendations: [ { title: 'Zaimplementuj buforowanie', description: 'Buforuj często dostępne zasoby', example: 'Dodaj nagłówki Cache-Control i zaimplementuj buforowanie service worker' }, { title: 'Użyj CDN', description: 'Obsługuj statyczne zasoby z CDN', example: 'Przenieś obrazy, CSS i JS do CDN dla szybszego dostarczania' }, { title: 'Włącz kompresję', description: 'Kompresuj odpowiedzi za pomocą gzip/brotli', example: 'app.use(compression()); // Middleware kompresji Express' }, { title: 'Optymalizuj payloady', description: 'Zmniejsz rozmiary odpowiedzi', example: 'Użyj paginacji, GraphQL lub sparse fieldsets' } ] }); } }
async analyzeRenderingPerformance() { if (!this.data.frontend) return;
const slowComponents = this.data.frontend.components .filter(c => c.avgRenderTime > 16) // Wolniejsze niż 60fps .sort((a, b) => b.totalTime - a.totalTime);
if (slowComponents.length > 0) { this.bottlenecks.push({ type: 'rendering', severity: slowComponents[0].avgRenderTime > 50 ? 'critical' : 'high', impact: slowComponents[0].totalTime / this.data.totalTime * 100, details: { slowComponents: slowComponents.slice(0, 5) }, recommendations: [ { title: 'Optymalizuj ponowne renderowanie', description: 'Użyj React.memo i useMemo', example: `const MemoizedComponent = React.memo(Component, (prevProps, nextProps) => { return prevProps.id === nextProps.id;});` }, { title: 'Wirtualizuj listy', description: 'Użyj wirtualnego przewijania dla długich list', example: 'Zaimplementuj react-window lub react-virtualized' }, { title: 'Dziel kod', description: 'Leniwie ładuj komponenty', example: `const HeavyComponent = React.lazy(() => import('./HeavyComponent'));` } ] }); } }
generateReport() { return { summary: { totalBottlenecks: this.bottlenecks.length, criticalCount: this.bottlenecks.filter(b => b.severity === 'critical').length, estimatedImpact: this.bottlenecks.reduce((sum, b) => sum + b.impact, 0) }, bottlenecks: this.bottlenecks, actionPlan: this.generateActionPlan() }; }
generateActionPlan() { const plan = [];
// Grupuj według typu i ważności const critical = this.bottlenecks.filter(b => b.severity === 'critical'); const high = this.bottlenecks.filter(b => b.severity === 'high');
if (critical.length > 0) { plan.push({ phase: 'Natychmiastowe', tasks: critical.map(b => ({ type: b.type, action: b.recommendations[0].title, impact: `Oczekiwana poprawa o ${b.impact.toFixed(1)}%` })) }); }
if (high.length > 0) { plan.push({ phase: 'Krótkoterminowe', tasks: high.map(b => ({ type: b.type, action: b.recommendations[0].title, impact: `Oczekiwana poprawa o ${b.impact.toFixed(1)}%` })) }); }
return plan; }}
Strategie optymalizacji
Dział zatytułowany „Strategie optymalizacji”Optymalizacja zapytań
Dział zatytułowany „Optymalizacja zapytań”> Optymalizuj nasze zapytania do bazy danych:> - Przeanalizuj wolne zapytania> - Dodaj odpowiednie indeksy> - Przepisz nieefektywne zapytania> - Zaimplementuj buforowanie zapytań> Pokaż metryki wydajności przed i po
class QueryOptimizer { constructor(db) { this.db = db; }
async optimizeQueries(slowQueries) { const optimizations = [];
for (const query of slowQueries) { const optimization = await this.optimizeQuery(query); if (optimization) { optimizations.push(optimization); } }
return optimizations; }
async optimizeQuery(queryInfo) { const { query, avgTime, count } = queryInfo;
// Analizuj strukturę zapytania const analysis = this.analyzeQuery(query);
// Generuj strategie optymalizacji const strategies = [];
// 1. Optymalizacja indeksów if (analysis.missingIndexes.length > 0) { strategies.push({ type: 'index', description: 'Dodaj brakujące indeksy', implementation: analysis.missingIndexes.map(idx => `CREATE INDEX ${idx.name} ON ${idx.table}(${idx.columns.join(', ')});` ), estimatedImprovement: 70 }); }
// 2. Przepisanie zapytania if (analysis.inefficientPatterns.length > 0) { strategies.push({ type: 'rewrite', description: 'Przepisz nieefektywne wzorce zapytań', implementation: this.rewriteQuery(query, analysis.inefficientPatterns), estimatedImprovement: 50 }); }
// 3. Buforowanie if (count > 10 && analysis.cacheable) { strategies.push({ type: 'cache', description: 'Zaimplementuj buforowanie wyników zapytań', implementation: this.generateCachingCode(query), estimatedImprovement: 90 }); }
// Testuj optymalizacje const results = await this.testOptimizations(query, strategies);
return { original: { query, avgTime, count }, optimizations: results, bestStrategy: results.reduce((best, current) => current.improvement > best.improvement ? current : best ) }; }
analyzeQuery(query) { const analysis = { tables: [], joins: [], whereConditions: [], orderBy: [], missingIndexes: [], inefficientPatterns: [], cacheable: true };
// Wyodrębnij komponenty zapytania const tableMatches = query.match(/FROM\s+(\w+)/gi); if (tableMatches) { analysis.tables = tableMatches.map(m => m.replace(/FROM\s+/i, '')); }
// Sprawdź brakujące indeksy w warunkach WHERE const whereMatch = query.match(/WHERE\s+(.+?)(?:ORDER|GROUP|LIMIT|$)/i); if (whereMatch) { const conditions = whereMatch[1].split(/\s+AND\s+/i); for (const condition of conditions) { const columnMatch = condition.match(/(\w+)\s*=|(\w+)\s+IN|(\w+)\s+LIKE/i); if (columnMatch) { const column = columnMatch[1] || columnMatch[2] || columnMatch[3]; analysis.whereConditions.push(column);
// Sprawdź czy indeks istnieje (uproszczone sprawdzenie) const indexExists = await this.checkIndexExists(analysis.tables[0], column); if (!indexExists) { analysis.missingIndexes.push({ name: `idx_${analysis.tables[0]}_${column}`, table: analysis.tables[0], columns: [column] }); } } } }
// Sprawdź nieefektywne wzorce if (query.includes('SELECT *')) { analysis.inefficientPatterns.push('select_all'); }
if (query.match(/NOT\s+IN\s*\(/i)) { analysis.inefficientPatterns.push('not_in'); }
if (query.match(/LIKE\s+'%[^']+'/i)) { analysis.inefficientPatterns.push('leading_wildcard'); }
// Sprawdź czy może być buforowane if (query.match(/NOW\(\)|CURRENT_|RAND\(\)/i)) { analysis.cacheable = false; }
return analysis; }
async checkIndexExists(table, column) { // Uproszczone sprawdzenie - w praktyce zapytaj katalogi systemowe try { const result = await this.db.query(` SELECT 1 FROM pg_indexes WHERE tablename = $1 AND indexdef LIKE '%${column}%' `, [table]); return result.rows.length > 0; } catch { return false; } }
rewriteQuery(query, patterns) { let optimized = query;
for (const pattern of patterns) { switch (pattern) { case 'select_all': // Zastąp SELECT * konkretnymi kolumnami optimized = optimized.replace( /SELECT\s+\*/i, 'SELECT id, name, created_at' // Przykładowe kolumny ); break;
case 'not_in': // Zastąp NOT IN za pomocą LEFT JOIN optimized = optimized.replace( /WHERE\s+(\w+)\s+NOT\s+IN\s*\(([^)]+)\)/i, (match, column, subquery) => `LEFT JOIN (${subquery}) excluded ON main.${column} = excluded.${column} WHERE excluded.${column} IS NULL` ); break;
case 'leading_wildcard': // Zasugeruj wyszukiwanie pełnotekstowe optimized = '-- Rozważ użycie wyszukiwania pełnotekstowego zamiast LIKE z początkowym wildcardzie\n' + optimized; break; } }
return optimized; }
generateCachingCode(query) { const cacheKey = this.generateCacheKey(query);
return `// Implementacja buforowania Redisconst cacheKey = '${cacheKey}';const cacheTTL = 3600; // 1 godzina
async function getCachedQuery(params) { // Sprawdź cache najpierw const cached = await redis.get(cacheKey); if (cached) { return JSON.parse(cached); }
// Wykonaj zapytanie const result = await db.query(\`${query}\`, params);
// Buforuj wynik await redis.setex(cacheKey, cacheTTL, JSON.stringify(result.rows));
return result.rows;}
// Unieważnij cache przy zmianach danychasync function invalidateCache() { await redis.del(cacheKey);}`; }
generateCacheKey(query) { // Generuj stabilny klucz cache z zapytania const normalized = query .replace(/\s+/g, ' ') .trim() .toLowerCase();
return `query:${crypto.createHash('md5').update(normalized).digest('hex')}`; }
async testOptimizations(originalQuery, strategies) { const results = [];
for (const strategy of strategies) { try { // Testuj optymalizację const testResult = await this.benchmarkQuery( strategy.type === 'rewrite' ? strategy.implementation : originalQuery, strategy );
results.push({ ...strategy, actualImprovement: testResult.improvement, newAvgTime: testResult.avgTime, successful: true }); } catch (error) { results.push({ ...strategy, error: error.message, successful: false }); } }
return results; }
async benchmarkQuery(query, optimization) { const iterations = 10; const times = [];
// Uruchom zapytanie wielokrotnie for (let i = 0; i < iterations; i++) { const start = performance.now(); await this.db.query(query); times.push(performance.now() - start); }
const avgTime = times.reduce((a, b) => a + b) / times.length; const improvement = optimization.estimatedImprovement || 0;
return { avgTime, improvement }; }}
Optymalizacja kodu
Dział zatytułowany „Optymalizacja kodu”> Optymalizuj kod naszej aplikacji pod kątem wydajności:> - Zidentyfikuj funkcje intensywnie wykorzystujące CPU> - Optymalizuj algorytmy> - Zaimplementuj buforowanie> - Dodaj równoległość> - Zmniejsz alokacje pamięci
class CodeOptimizer { analyzeAndOptimize(code, profilerData) { const optimizations = [];
// 1. Optymalizacja algorytmów const algorithmOptimizations = this.optimizeAlgorithms(code, profilerData); optimizations.push(...algorithmOptimizations);
// 2. Możliwości buforowania const cachingOptimizations = this.addCaching(code, profilerData); optimizations.push(...cachingOptimizations);
// 3. Równoległość const parallelOptimizations = this.addParallelization(code); optimizations.push(...parallelOptimizations);
// 4. Optymalizacja pamięci const memoryOptimizations = this.optimizeMemory(code); optimizations.push(...memoryOptimizations);
return optimizations; }
optimizeAlgorithms(code, profilerData) { const optimizations = [];
// Przykład: Zastąp O(n²) za pomocą O(n log n) if (code.includes('filter') && code.includes('includes')) { optimizations.push({ type: 'algorithm', pattern: 'nested array search', original: `// Złożoność O(n²)const filtered = array1.filter(item => array2.includes(item.id));`, optimized: `// Złożoność O(n) używając Setconst array2Set = new Set(array2);const filtered = array1.filter(item => array2Set.has(item.id));`, improvement: 'Z O(n²) do O(n)' }); }
// Przykład: Optymalizuj sortowanie if (code.match(/sort\([^)]*\).*sort\([^)]*\)/)) { optimizations.push({ type: 'algorithm', pattern: 'multiple sorts', original: `// Wielokrotne sortowaniadata.sort((a, b) => a.date - b.date) .sort((a, b) => a.priority - b.priority);`, optimized: `// Pojedyncze kombinowane sortowaniedata.sort((a, b) => { if (a.priority !== b.priority) { return a.priority - b.priority; } return a.date - b.date;});`, improvement: 'Zmniejszone z 2 przejść do 1' }); }
return optimizations; }
addCaching(code, profilerData) { const optimizations = [];
// Memoizacja dla kosztownych funkcji const expensiveFunctions = profilerData.functions .filter(f => f.avgTime > 10 && f.calls > 10);
for (const func of expensiveFunctions) { optimizations.push({ type: 'caching', pattern: 'memoization', original: `function ${func.name}(input) { // Kosztowne obliczenie return result;}`, optimized: `const ${func.name} = (() => { const cache = new Map();
return function(input) { const key = JSON.stringify(input); if (cache.has(key)) { return cache.get(key); }
// Kosztowne obliczenie const result = computeResult(input); cache.set(key, result);
// Wykluczanie LRU if (cache.size > 1000) { const firstKey = cache.keys().next().value; cache.delete(firstKey); }
return result; };})();`, improvement: 'Unikaj powtarzających się kalkulacji' }); }
return optimizations; }
addParallelization(code) { const optimizations = [];
// Szukaj sekwencyjnych operacji async if (code.match(/await.*\n.*await/)) { optimizations.push({ type: 'parallelization', pattern: 'sequential awaits', original: `// Wykonanie sekwencyjneconst user = await fetchUser(id);const posts = await fetchPosts(id);const comments = await fetchComments(id);`, optimized: `// Wykonanie równoległeconst [user, posts, comments] = await Promise.all([ fetchUser(id), fetchPosts(id), fetchComments(id)]);`, improvement: '3x szybciej dla niezależnych operacji' }); }
// Worker threads dla zadań intensywnie wykorzystujących CPU if (code.includes('for') && code.includes('compute')) { optimizations.push({ type: 'parallelization', pattern: 'cpu-intensive loop', original: `// Przetwarzanie jednowątkoweconst results = [];for (const item of largeArray) { results.push(expensiveComputation(item));}`, optimized: `// Przetwarzanie wielowątkoweconst { Worker } = require('worker_threads');const os = require('os');
async function parallelProcess(items) { const numWorkers = os.cpus().length; const chunkSize = Math.ceil(items.length / numWorkers); const chunks = [];
for (let i = 0; i < items.length; i += chunkSize) { chunks.push(items.slice(i, i + chunkSize)); }
const workers = chunks.map(chunk => new Promise((resolve, reject) => { const worker = new Worker('./computation-worker.js'); worker.postMessage(chunk); worker.on('message', resolve); worker.on('error', reject); }) );
const results = await Promise.all(workers); return results.flat();}`, improvement: `${os.cpus().length}x przyspiesznie dla zadań CPU-bound` }); }
return optimizations; }
optimizeMemory(code) { const optimizations = [];
// Object pooling if (code.includes('new') && code.includes('loop')) { optimizations.push({ type: 'memory', pattern: 'object allocation in loop', original: `// Tworzy śmiecifor (let i = 0; i < 1000000; i++) { const obj = new ExpensiveObject(); process(obj);}`, optimized: `// Pool obiektówclass ObjectPool { constructor(createFn, resetFn, size = 100) { this.createFn = createFn; this.resetFn = resetFn; this.pool = []; this.available = [];
// Wstępnie wypełnij pool for (let i = 0; i < size; i++) { const obj = createFn(); this.pool.push(obj); this.available.push(obj); } }
acquire() { if (this.available.length > 0) { return this.available.pop(); } return this.createFn(); }
release(obj) { this.resetFn(obj); if (this.available.length < this.pool.length) { this.available.push(obj); } }}
const objectPool = new ObjectPool( () => new ExpensiveObject(), (obj) => obj.reset());
for (let i = 0; i < 1000000; i++) { const obj = objectPool.acquire(); process(obj); objectPool.release(obj);}`, improvement: 'Zmniejszona presja GC i narzut alokacji' }); }
// Łączenie stringów if (code.match(/\+=.*string|str\s*\+/)) { optimizations.push({ type: 'memory', pattern: 'string concatenation', original: `// Nieefektywne budowanie stringówlet result = '';for (const item of items) { result += item + ',';}`, optimized: `// Efektywne budowanie stringówconst parts = [];for (const item of items) { parts.push(item);}const result = parts.join(',');
// Lub dla bardzo dużych stringówconst { StringDecoder } = require('string_decoder');const decoder = new StringDecoder('utf8');const buffers = [];// ... dodaj buffersconst result = Buffer.concat(buffers).toString();`, improvement: 'Unikaj pośrednich alokacji stringów' }); }
return optimizations; }}
Testowanie obciążeniowe
Dział zatytułowany „Testowanie obciążeniowe”Automatyczne testowanie obciążeniowe
Dział zatytułowany „Automatyczne testowanie obciążeniowe”> Utwórz kompleksowy zestaw testów obciążeniowych:> - Symuluj realistyczne zachowanie użytkowników> - Testuj pod różnymi warunkami obciążenia> - Zidentyfikuj punkty krytyczne> - Generuj raporty wydajności
const autocannon = require('autocannon');const { Worker } = require('worker_threads');
class LoadTester { constructor(config) { this.config = { baseUrl: config.baseUrl || 'http://localhost:3000', duration: config.duration || 60, scenarios: config.scenarios || [] };
this.results = []; }
async runLoadTests() { console.log('Rozpoczynam testy obciążeniowe...\n');
// Uruchom różne scenariusze obciążenia for (const scenario of this.config.scenarios) { console.log(`Uruchamiam scenariusz: ${scenario.name}`); const result = await this.runScenario(scenario); this.results.push(result);
// Ochłodzenie między scenariuszami await this.coolDown(10); }
return this.generateReport(); }
async runScenario(scenario) { const instance = autocannon({ url: this.config.baseUrl + scenario.endpoint, connections: scenario.connections || 10, pipelining: scenario.pipelining || 1, duration: scenario.duration || this.config.duration, requests: scenario.requests, setupClient: this.setupClient.bind(this), // Niestandardowa logika scenariusza requests: this.generateRequests(scenario) });
return new Promise((resolve) => { instance.on('done', (results) => { resolve({ scenario: scenario.name, results: this.processResults(results), metrics: this.calculateMetrics(results) }); }); }); }
setupClient(client) { // Dodaj uwierzytelnianie jeśli potrzebne client.on('headers', (headers) => { headers['Authorization'] = 'Bearer ' + this.config.authToken; }); }
generateRequests(scenario) { const requests = [];
// Generuj realistyczne wzorce żądań if (scenario.type === 'user-journey') { requests.push( // Login { method: 'POST', path: '/api/auth/login', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: 'test@example.com', password: 'password123' }) }, // Przeglądaj produkty { method: 'GET', path: '/api/products?page=1&limit=20', setupRequest: (req, context) => { req.headers['Authorization'] = `Bearer ${context.token}`; } }, // Dodaj do koszyka { method: 'POST', path: '/api/cart/add', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ productId: '123', quantity: 1 }) } ); }
return requests; }
processResults(results) { return { requests: { total: results.requests.total, average: results.requests.average, mean: results.requests.mean, stddev: results.requests.stddev, min: results.requests.min, max: results.requests.max, p99: results.requests.p99, p95: results.requests.p95, p50: results.requests.p50 }, throughput: { average: results.throughput.average, mean: results.throughput.mean, stddev: results.throughput.stddev, min: results.throughput.min, max: results.throughput.max, p99: results.throughput.p99 }, latency: { average: results.latency.average, mean: results.latency.mean, stddev: results.latency.stddev, min: results.latency.min, max: results.latency.max, p99: results.latency.p99, p95: results.latency.p95, p50: results.latency.p50 }, errors: results.errors, timeouts: results.timeouts, duration: results.duration, connections: results.connections, pipelining: results.pipelining }; }
calculateMetrics(results) { const metrics = { // Żądania na sekundę rps: results.requests.average,
// Wskaźnik sukcesu successRate: ((results.requests.total - results.errors) / results.requests.total) * 100,
// Średni czas odpowiedzi avgResponseTime: results.latency.mean,
// Percentylowe czasy odpowiedzi p50ResponseTime: results.latency.p50, p95ResponseTime: results.latency.p95, p99ResponseTime: results.latency.p99,
// Przepustowość throughputMBps: results.throughput.mean / 1024 / 1024,
// Wskaźnik błędów errorRate: (results.errors / results.requests.total) * 100,
// Wskaźnik timeoutów timeoutRate: (results.timeouts / results.requests.total) * 100 };
// Oblicz zgodność z SLA metrics.slaCompliance = this.calculateSLACompliance(metrics);
return metrics; }
calculateSLACompliance(metrics) { const sla = { maxResponseTime: 500, // ms minSuccessRate: 99.9, // % maxErrorRate: 0.1 // % };
const compliance = { responseTime: metrics.p95ResponseTime <= sla.maxResponseTime, successRate: metrics.successRate >= sla.minSuccessRate, errorRate: metrics.errorRate <= sla.maxErrorRate, overall: true };
compliance.overall = compliance.responseTime && compliance.successRate && compliance.errorRate;
return compliance; }
async coolDown(seconds) { console.log(`Ochładzanie przez ${seconds} sekund...`); await new Promise(resolve => setTimeout(resolve, seconds * 1000)); }
generateReport() { const report = { timestamp: new Date().toISOString(), summary: this.generateSummary(), scenarios: this.results, recommendations: this.generateRecommendations() };
// Generuj wizualizacje report.charts = this.generateCharts();
return report; }
generateSummary() { const summary = { totalScenarios: this.results.length, overallSuccess: true, failedScenarios: [], performance: { avgRPS: 0, avgResponseTime: 0, avgSuccessRate: 0 } };
let totalRPS = 0; let totalResponseTime = 0; let totalSuccessRate = 0;
for (const result of this.results) { totalRPS += result.metrics.rps; totalResponseTime += result.metrics.avgResponseTime; totalSuccessRate += result.metrics.successRate;
if (!result.metrics.slaCompliance.overall) { summary.overallSuccess = false; summary.failedScenarios.push(result.scenario); } }
summary.performance.avgRPS = totalRPS / this.results.length; summary.performance.avgResponseTime = totalResponseTime / this.results.length; summary.performance.avgSuccessRate = totalSuccessRate / this.results.length;
return summary; }
generateRecommendations() { const recommendations = [];
for (const result of this.results) { const metrics = result.metrics;
// Rekomendacje dla czasu odpowiedzi if (metrics.p95ResponseTime > 1000) { recommendations.push({ scenario: result.scenario, issue: 'Wysoki czas odpowiedzi', recommendation: 'Optymalizuj wolne endpointy, dodaj buforowanie lub skaluj horyzontalnie', severity: 'high' }); }
// Rekomendacje dla wskaźnika błędów if (metrics.errorRate > 1) { recommendations.push({ scenario: result.scenario, issue: 'Wysoki wskaźnik błędów', recommendation: 'Zbadaj logi błędów, napraw bugi, dodaj logikę ponawiania', severity: 'critical' }); }
// Rekomendacje dla przepustowości if (metrics.rps < 100) { recommendations.push({ scenario: result.scenario, issue: 'Niska przepustowość', recommendation: 'Zwiększ pulę połączeń, optymalizuj zapytania do bazy danych, dodaj buforowanie', severity: 'medium' }); } }
return recommendations; }
generateCharts() { // Generuj dane wykresów do wizualizacji return { responseTimeChart: { type: 'line', data: this.results.map(r => ({ scenario: r.scenario, p50: r.results.latency.p50, p95: r.results.latency.p95, p99: r.results.latency.p99 })) }, throughputChart: { type: 'bar', data: this.results.map(r => ({ scenario: r.scenario, rps: r.metrics.rps })) }, errorRateChart: { type: 'bar', data: this.results.map(r => ({ scenario: r.scenario, errorRate: r.metrics.errorRate })) } }; }}
// Użycieconst loadTester = new LoadTester({ baseUrl: 'http://localhost:3000', duration: 60, scenarios: [ { name: 'Lekkie obciążenie', endpoint: '/api/products', connections: 10, duration: 30 }, { name: 'Normalne obciążenie', endpoint: '/api/products', connections: 100, duration: 60 }, { name: 'Ciężkie obciążenie', endpoint: '/api/products', connections: 500, duration: 60 }, { name: 'Test skoków', endpoint: '/api/products', connections: 1000, duration: 30 }, { name: 'Podróż użytkownika', type: 'user-journey', connections: 50, duration: 120 } ]});
loadTester.runLoadTests().then(report => { console.log(JSON.stringify(report, null, 2));});
Monitorowanie wydajności
Dział zatytułowany „Monitorowanie wydajności”Ciągłe monitorowanie wydajności
Dział zatytułowany „Ciągłe monitorowanie wydajności”> Skonfiguruj ciągłe monitorowanie wydajności:> - Zbieranie metryk w czasie rzeczywistym> - Budżety wydajności> - Automatyczne alerty> - Analiza trendów> - Wykrywanie regresji
class PerformanceDashboard { constructor() { this.metrics = { realtime: new Map(), historical: [], budgets: this.loadBudgets(), alerts: [] };
this.startMonitoring(); }
loadBudgets() { return { responseTime: { p50: 200, p95: 500, p99: 1000 }, errorRate: 0.1, // 0.1% cpu: 70, // 70% memory: 80, // 80% bundleSize: { js: 500 * 1024, // 500KB css: 100 * 1024, // 100KB total: 1024 * 1024 // 1MB }, webVitals: { LCP: 2500, FID: 100, CLS: 0.1 } }; }
startMonitoring() { // Zbieraj metryki co minutę setInterval(() => { this.collectMetrics(); this.checkBudgets(); this.detectRegressions(); }, 60000);
// Monitorowanie w czasie rzeczywistym this.setupRealtimeMonitoring(); }
async collectMetrics() { const timestamp = Date.now(); const metrics = { timestamp, server: await this.collectServerMetrics(), database: await this.collectDatabaseMetrics(), frontend: await this.collectFrontendMetrics(), business: await this.collectBusinessMetrics() };
this.metrics.historical.push(metrics);
// Zachowaj tylko ostatnie 24 godziny const dayAgo = timestamp - 24 * 60 * 60 * 1000; this.metrics.historical = this.metrics.historical .filter(m => m.timestamp > dayAgo); }
async collectServerMetrics() { // Zbieraj z endpointów monitorowania const response = await fetch('/metrics'); const data = await response.json();
return { responseTime: { p50: data.http_request_duration_p50, p95: data.http_request_duration_p95, p99: data.http_request_duration_p99 }, requestRate: data.http_requests_per_second, errorRate: data.http_error_rate, cpu: data.cpu_usage_percent, memory: data.memory_usage_percent, activeConnections: data.active_connections }; }
async collectDatabaseMetrics() { const response = await fetch('/metrics/database'); const data = await response.json();
return { queryTime: { avg: data.avg_query_time, p95: data.p95_query_time }, activeConnections: data.active_connections, slowQueries: data.slow_query_count, deadlocks: data.deadlock_count }; }
checkBudgets() { const latest = this.metrics.historical[this.metrics.historical.length - 1]; if (!latest) return;
const violations = [];
// Sprawdź budżety czasu odpowiedzi if (latest.server.responseTime.p95 > this.metrics.budgets.responseTime.p95) { violations.push({ metric: 'Response Time P95', value: latest.server.responseTime.p95, budget: this.metrics.budgets.responseTime.p95, severity: 'high' }); }
// Sprawdź wskaźnik błędów if (latest.server.errorRate > this.metrics.budgets.errorRate) { violations.push({ metric: 'Error Rate', value: latest.server.errorRate, budget: this.metrics.budgets.errorRate, severity: 'critical' }); }
// Sprawdź wykorzystanie zasobów if (latest.server.cpu > this.metrics.budgets.cpu) { violations.push({ metric: 'CPU Usage', value: latest.server.cpu, budget: this.metrics.budgets.cpu, severity: 'medium' }); }
if (violations.length > 0) { this.handleBudgetViolations(violations); } }
handleBudgetViolations(violations) { for (const violation of violations) { // Utwórz alert const alert = { id: Date.now() + Math.random(), timestamp: Date.now(), type: 'budget_violation', ...violation };
this.metrics.alerts.push(alert);
// Wyślij powiadomienia if (violation.severity === 'critical') { this.sendAlert(alert); } } }
detectRegressions() { if (this.metrics.historical.length < 10) return;
// Porównaj obecną wydajność z historyczną bazą const recent = this.metrics.historical.slice(-5); const baseline = this.metrics.historical.slice(-50, -10);
const recentAvg = this.calculateAverages(recent); const baselineAvg = this.calculateAverages(baseline);
// Wykryj znaczące regresje const regressions = [];
// Regresja czasu odpowiedzi const responseTimeIncrease = (recentAvg.responseTime - baselineAvg.responseTime) / baselineAvg.responseTime;
if (responseTimeIncrease > 0.2) { // 20% wzrost regressions.push({ metric: 'Response Time', baseline: baselineAvg.responseTime, current: recentAvg.responseTime, change: `+${(responseTimeIncrease * 100).toFixed(1)}%`, severity: responseTimeIncrease > 0.5 ? 'high' : 'medium' }); }
// Regresja wskaźnika błędów if (recentAvg.errorRate > baselineAvg.errorRate * 2) { regressions.push({ metric: 'Error Rate', baseline: baselineAvg.errorRate, current: recentAvg.errorRate, change: `+${((recentAvg.errorRate - baselineAvg.errorRate) * 100).toFixed(2)}%`, severity: 'high' }); }
if (regressions.length > 0) { this.handleRegressions(regressions); } }
calculateAverages(metrics) { const avg = { responseTime: 0, errorRate: 0, cpu: 0, memory: 0 };
for (const metric of metrics) { avg.responseTime += metric.server.responseTime.p95; avg.errorRate += metric.server.errorRate; avg.cpu += metric.server.cpu; avg.memory += metric.server.memory; }
const count = metrics.length; avg.responseTime /= count; avg.errorRate /= count; avg.cpu /= count; avg.memory /= count;
return avg; }
setupRealtimeMonitoring() { // Połączenie WebSocket dla metryk czasu rzeczywistego const ws = new WebSocket('ws://localhost:3000/metrics/stream');
ws.on('message', (data) => { const metric = JSON.parse(data);
// Aktualizuj metryki czasu rzeczywistego this.metrics.realtime.set(metric.type, metric);
// Sprawdź natychmiastowe problemy if (metric.type === 'error_spike') { this.handleErrorSpike(metric); } }); }
generateDashboard() { const latest = this.metrics.historical[this.metrics.historical.length - 1]; const realtime = Object.fromEntries(this.metrics.realtime);
return { timestamp: Date.now(), current: { server: latest?.server || {}, database: latest?.database || {}, realtime }, trends: this.calculateTrends(), alerts: this.metrics.alerts.slice(-10), recommendations: this.generateRecommendations() }; }
calculateTrends() { if (this.metrics.historical.length < 2) return {};
const current = this.metrics.historical[this.metrics.historical.length - 1]; const hourAgo = this.metrics.historical[this.metrics.historical.length - 60] || this.metrics.historical[0];
return { responseTime: { current: current.server.responseTime.p95, hourAgo: hourAgo.server.responseTime.p95, trend: current.server.responseTime.p95 > hourAgo.server.responseTime.p95 ? 'up' : 'down' }, errorRate: { current: current.server.errorRate, hourAgo: hourAgo.server.errorRate, trend: current.server.errorRate > hourAgo.server.errorRate ? 'up' : 'down' }, traffic: { current: current.server.requestRate, hourAgo: hourAgo.server.requestRate, trend: current.server.requestRate > hourAgo.server.requestRate ? 'up' : 'down' } }; }
generateRecommendations() { const recommendations = []; const latest = this.metrics.historical[this.metrics.historical.length - 1];
if (!latest) return recommendations;
// Wysokie wykorzystanie CPU if (latest.server.cpu > 80) { recommendations.push({ type: 'scaling', priority: 'high', message: 'Wykorzystanie CPU jest wysokie. Rozważ skalowanie horyzontalne lub optymalizację kodu.', action: 'Skaluj instancje w górę lub optymalizuj operacje intensywnie wykorzystujące CPU' }); }
// Wolne zapytania do bazy danych if (latest.database.slowQueries > 10) { recommendations.push({ type: 'database', priority: 'medium', message: `Wykryto ${latest.database.slowQueries} wolnych zapytań.`, action: 'Przejrzyj i optymalizuj wolne zapytania do bazy danych' }); }
// Presja pamięci if (latest.server.memory > 85) { recommendations.push({ type: 'memory', priority: 'high', message: 'Wykorzystanie pamięci zbliża się do limitów.', action: 'Zbadaj wycieki pamięci lub zwiększ alokację pamięci' }); }
return recommendations; }}
Powiązane lekcje
Dział zatytułowany „Powiązane lekcje”Następne kroki
Dział zatytułowany „Następne kroki”Nauczyłeś się, jak wykorzystać Claude Code do kompleksowej analizy i optymalizacji wydajności. Kluczem jest traktowanie wydajności jako ciągłego procesu, a nie jednorazowej optymalizacji. Buduj świadomość wydajności w każdej linii kodu, każdej decyzji architektonicznej, każdym wdrożeniu.
Pamiętaj: Wydajność to funkcja. Użytkownicy nie obchodzi, jak sprytny jest Twój kod, jeśli jest wolny. Użyj Claude Code do systematycznego znajdowania i naprawiania wąskich gardeł, zapewniając, że Twoja aplikacja zachwyca użytkowników swoją szybkością i responsywnością.