Usamos cookies para mejorar tu experiencia en el sitio
CodeWorlds

Profiling i Benchmarking NestJS - diagnostyka mocy Imperium

Dowódco legionów! Nawet najlepiej wyszkolona armia rzymska potrzebuje regularnych przeglądów - sprawdzenia kondycji żołnierzy, stanu uzbrojenia i efektywności strategii. W NestJS profiling i benchmarking to identyczna inspekcja - diagnozujesz gdzie Twoja aplikacja traci czas, pamięć i zasoby.

Benchmarking z autocannon

autocannon
to narzędzie do testów obciążeniowych (load testing) - jak wysłanie tysięcy posłańców jednocześnie do sprawdzenia przepustowości dróg Imperium:

1// benchmark.ts - skrypt benchmarkowy
2// Instalacja: npm install -g autocannon
3
4// Uruchomienie z terminala:
5// autocannon -c 100 -d 30 -p 10 http://localhost:3000/legions
6//
7// -c 100  = 100 równoległych połączeń (100 posłańców naraz)
8// -d 30   = test trwa 30 sekund
9// -p 10   = 10 pipelined żądań na połączenie
10
11// Wynik:
12// Stat    2.5%   50%    97.5%  99%    Avg     Stdev   Max
13// Latency 12ms   25ms   89ms   120ms  32.4ms  18.2ms  250ms
14// Req/Sec 850    1200   1450   1500   1180    185     1520
15//
16// 35,400 requests in 30s, 12.5 MB read
17
18// Programistyczne użycie autocannon w testach
19import * as autocannon from 'autocannon';
20
21async function runBenchmark() {
22  const result = await autocannon({
23    url: 'http://localhost:3000/legions',
24    connections: 100,       // 100 równoległych połączeń
25    duration: 10,           // 10 sekund testu
26    pipelining: 10,         // 10 żądań w pipeline
27    headers: {
28      'Authorization': 'Bearer test-token',
29    },
30  });
31
32  console.log('=== Wyniki Benchmarku ===');
33  console.log('Requests/sec:', result.requests.average);
34  console.log('Latency avg:', result.latency.average, 'ms');
35  console.log('Latency p99:', result.latency.p99, 'ms');
36  console.log('Throughput:', result.throughput.average, 'bytes/sec');
37  console.log('Errors:', result.errors);
38  console.log('Timeouts:', result.timeouts);
39
40  // Kryteria sukcesu
41  if (result.latency.p99 > 200) {
42    console.warn('UWAGA: p99 latency przekracza 200ms!');
43  }
44  if (result.errors > 0) {
45    console.error('BLAD: Wystąpiły błędy podczas testu!');
46  }
47}
48
49runBenchmark();

Node.js --inspect Profiling

Node.js ma wbudowany profiler, który pozwala analizować co robi CPU w trakcie działania aplikacji:

1// Uruchomienie NestJS z profilerem:
2// node --inspect dist/main.js
3//
4// Następnie otwórz Chrome: chrome://inspect
5// Kliknij "Open dedicated DevTools for Node"
6//
7// W zakładce "Performance":
8// 1. Kliknij "Record"
9// 2. Wykonaj operacje na API (wyślij żądania)
10// 3. Kliknij "Stop"
11// 4. Analizuj flame graph
12
13// Programistyczne profilowanie CPU
14import { Session } from 'inspector';
15import { writeFileSync } from 'fs';
16
17class CpuProfiler {
18  private session: Session;
19
20  constructor() {
21    this.session = new Session();
22    this.session.connect();
23  }
24
25  async startProfiling(): Promise<void> {
26    await new Promise<void>((resolve) => {
27      this.session.post('Profiler.enable', () => {
28        this.session.post('Profiler.start', () => {
29          console.log('Profiling CPU started...');
30          resolve();
31        });
32      });
33    });
34  }
35
36  async stopProfiling(filename: string): Promise<void> {
37    return new Promise((resolve) => {
38      this.session.post('Profiler.stop', (err, { profile }) => {
39        if (!err) {
40          writeFileSync(filename, JSON.stringify(profile));
41          console.log('Profil CPU zapisany do: ' + filename);
42        }
43        resolve();
44      });
45    });
46  }
47}
48
49// Użycie:
50// const profiler = new CpuProfiler();
51// await profiler.startProfiling();
52// ... wykonaj operacje ...
53// await profiler.stopProfiling('cpu-profile.cpuprofile');
54// Otwórz plik w Chrome DevTools -> Performance

Flame Graphs - wizualizacja hot spots

Flame graphs pokazują graficznie, które funkcje zajmują najwięcej czasu CPU - jak mapa ciepła bitwy pokazująca gdzie toczy się najcięższa walka:

1// Generowanie flame graph z clinic.js
2// npm install -g clinic
3
4// 1. clinic doctor - ogólna diagnoza
5// clinic doctor -- node dist/main.js
6// (w drugim terminalu: autocannon -c 100 -d 10 http://localhost:3000/legions)
7// Ctrl+C -> otwiera raport HTML
8
9// 2. clinic flame - flame graph
10// clinic flame -- node dist/main.js
11// (obciąż aplikację jak wyżej)
12// Wynik: interaktywny flame graph
13
14// 3. clinic bubbleprof - analiza async operacji
15// clinic bubbleprof -- node dist/main.js
16// Wynik: wizualizacja opóźnień async (np. zapytania DB)
17
18// Interpretacja Flame Graph:
19// - Szerokość bloku = czas CPU (szerszy = dłuższy)
20// - Wysokość = głębokość call stacka
21// - Kolor czerwony = hot path (dużo czasu)
22// - Szukaj szerokich bloków na dole - to bottlenecki!

Heap Snapshots - analiza pamięci

Heap snapshots to zrzuty pamięci aplikacji - jak spis ludności Imperium, który pokazuje kto zajmuje ile miejsca:

1// heap-snapshot.service.ts
2import { Injectable, Logger } from '@nestjs/common';
3import * as v8 from 'v8';
4import { writeFileSync } from 'fs';
5
6@Injectable()
7export class HeapSnapshotService {
8  private readonly logger = new Logger(HeapSnapshotService.name);
9
10  // Zrób zrzut pamięci
11  takeSnapshot(filename?: string): string {
12    const snapshotFile = filename ||
13      'heap-' + new Date().toISOString().replace(/[:.]/g, '-') + '.heapsnapshot';
14
15    const snapshotStream = v8.writeHeapSnapshot(snapshotFile);
16    this.logger.log('Heap snapshot zapisany: ' + snapshotStream);
17
18    return snapshotStream;
19  }
20
21  // Pobierz statystyki heap
22  getHeapStatistics() {
23    const stats = v8.getHeapStatistics();
24    return {
25      totalHeapSize: Math.round(stats.total_heap_size / 1024 / 1024) + ' MB',
26      usedHeapSize: Math.round(stats.used_heap_size / 1024 / 1024) + ' MB',
27      heapSizeLimit: Math.round(stats.heap_size_limit / 1024 / 1024) + ' MB',
28      mallocedMemory: Math.round(stats.malloced_memory / 1024 / 1024) + ' MB',
29      usagePercent: ((stats.used_heap_size / stats.heap_size_limit) * 100).toFixed(1) + '%',
30    };
31  }
32
33  // Porównanie dwóch snapshotów dla wykrycia memory leaks
34  compareSnapshots() {
35    const before = v8.getHeapStatistics();
36    // ... wykonaj operacje ...
37    const after = v8.getHeapStatistics();
38
39    const diff = {
40      heapGrowth: after.used_heap_size - before.used_heap_size,
41      objectGrowth: after.number_of_native_contexts - before.number_of_native_contexts,
42    };
43
44    if (diff.heapGrowth > 10 * 1024 * 1024) { // > 10MB growth
45      this.logger.warn('Potencjalny memory leak! Heap wzrósł o ' +
46        Math.round(diff.heapGrowth / 1024 / 1024) + ' MB');
47    }
48
49    return diff;
50  }
51}

Identyfikowanie bottlenecków - praktyczny workflow

Oto kompletny workflow diagnostyczny, jakiego używa doświadczony inżynier Imperium:

1// performance-diagnostic.service.ts
2import { Injectable, Logger } from '@nestjs/common';
3
4@Injectable()
5export class PerformanceDiagnosticService {
6  private readonly logger = new Logger('PerformanceDiagnostic');
7
8  // Krok 1: Zmierz czas endpointów
9  async measureEndpoint(name: string, operation: () => Promise<any>) {
10    const start = process.hrtime.bigint();
11    const result = await operation();
12    const end = process.hrtime.bigint();
13    const durationMs = Number(end - start) / 1_000_000;
14
15    this.logger.log(name + ': ' + durationMs.toFixed(2) + 'ms');
16
17    if (durationMs > 200) {
18      this.logger.warn(name + ' przekracza 200ms - wymaga optymalizacji!');
19    }
20
21    return { result, durationMs };
22  }
23
24  // Krok 2: Sprawdź zużycie pamięci
25  checkMemoryUsage() {
26    const usage = process.memoryUsage();
27    return {
28      rss: Math.round(usage.rss / 1024 / 1024) + ' MB',
29      heapUsed: Math.round(usage.heapUsed / 1024 / 1024) + ' MB',
30      heapTotal: Math.round(usage.heapTotal / 1024 / 1024) + ' MB',
31      external: Math.round(usage.external / 1024 / 1024) + ' MB',
32    };
33  }
34
35  // Krok 3: Monitoruj event loop delay
36  measureEventLoopDelay(): Promise<number> {
37    return new Promise((resolve) => {
38      const start = Date.now();
39      setImmediate(() => {
40        const delay = Date.now() - start;
41        if (delay > 10) {
42          this.logger.warn('Event loop delay: ' + delay + 'ms');
43        }
44        resolve(delay);
45      });
46    });
47  }
48
49  // Krok 4: Pełny raport diagnostyczny
50  async generateDiagnosticReport() {
51    const memory = this.checkMemoryUsage();
52    const eventLoopDelay = await this.measureEventLoopDelay();
53    const uptime = process.uptime();
54
55    return {
56      timestamp: new Date().toISOString(),
57      uptime: Math.round(uptime) + 's',
58      memory,
59      eventLoopDelay: eventLoopDelay + 'ms',
60      nodeVersion: process.version,
61      platform: process.platform,
62    };
63  }
64}

Profiling i benchmarking to oczy i uszy Imperium na polu wydajności. Jak mądry strateg analizuje pole bitwy przed atakiem, tak dobry inżynier profiluje aplikację przed optymalizacją. Pamiętaj zasadę Pretora Augusta: "Nie optymalizuj tego, czego nie zmierzyłeś!"

Ir a CodeWorlds