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.
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 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 -> PerformanceFlame 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 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}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ś!"