eBPF Off-CPU Analýza: Nájdenie Latencie Ktorú Metriky Nevidia
CPU bol idle, requesty boli pomale a eBPF konecne vysvetlil rozdiel. “CPU využitie je 20% ale p99 latencia je 500ms.” Štandardné profilery ukazujú len CPU čas. Vaša aplikácia čaká - na zámky, I/O, alebo iné procesy. eBPF off-CPU analýza odhaľuje kam skutočne ide čas.
Testované na: Linux 5.15, bcc-tools 0.28, Go 1.21 aplikácia s mutex contention
Pochopenie Off-CPU Času
CPU Čas vs Wall Čas
Životný cyklus requestu:
Wall čas: 500ms (čo zažíva používateľ)
├── CPU čas: 100ms (čo vidia štandardné profilery)
│ ├── 50ms: JSON parsing
│ ├── 30ms: Business logika
│ └── 20ms: Serializácia odpovede
│
└── Off-CPU čas: 400ms (NEVIDITEĽNÝ pre CPU profilery!)
├── 200ms: Čakanie na databázu
├── 150ms: Blokovaný na mutexe
└── 50ms: Network I/O wait
CPU profiler hovorí: "JSON parsing je 50% CPU času"
Realita: "Mutex contention je 30% času requestu"
Prečo Štandardné Nástroje Toto Nevidia
Čo ukazuje pprof (CPU profiler):
┌─────────────────────────────────────────────────────────────┐
│ Funkcia │ CPU Čas │ % Celkom │
├─────────────────────────────────────────────────────────────┤
│ encoding/json.Unmarshal │ 50ms │ 50% │
│ main.processOrder │ 30ms │ 30% │
│ net/http.writeResponse │ 20ms │ 20% │
└─────────────────────────────────────────────────────────────┘
Celkom: 100ms (ale request trval 500ms!)
Čo ukazuje eBPF off-CPU:
┌─────────────────────────────────────────────────────────────┐
│ Dôvod Čakania │ Off-CPU │ % Celkom │
├─────────────────────────────────────────────────────────────┤
│ futex (mutex) │ 150ms │ 30% │
│ epoll_wait (sieť) │ 200ms │ 40% │
│ read (disk) │ 50ms │ 10% │
│ On-CPU │ 100ms │ 20% │
└─────────────────────────────────────────────────────────────┘
Celkom: 500ms (úplný obraz!)
Použitie BCC Tools
Inštalácia
# Ubuntu/Debian
apt-get install bpfcc-tools linux-headers-$(uname -r)
# RHEL/CentOS
yum install bcc-tools
# Nástroje sú v /usr/share/bcc/tools/
# Alebo spusti s: python3 /usr/share/bcc/tools/offcputime
Základná Off-CPU Analýza
# Nájdi PID tvojej aplikácie
pgrep -f "myapp"
# Príklad: 12345
# Zachyť off-CPU stacky na 30 sekúnd
/usr/share/bcc/tools/offcputime -df -p 12345 30 > offcpu.stacks
# Výstupný formát:
# stack_trace;stack_trace;... čas_v_mikrosekundách
# Príklad:
# main.handler;sync.Mutex.Lock;runtime.futex 150000
# main.handler;database/sql.Query;net.Read 200000
Vizualizácia s Flame Graph
# Nainštaluj flamegraph tools
git clone https://github.com/brendangregg/FlameGraph
# Vygeneruj flame graph
./FlameGraph/flamegraph.pl --color=io \
--title="Off-CPU Time" \
offcpu.stacks > offcpu.svg
# Otvor v prehliadači - ČERVENÁ/ORANŽOVÁ ukazuje off-CPU čas
Live Analýza
# Real-time off-CPU súhrn
/usr/share/bcc/tools/offcputime -p 12345 --state 2
# --state 2 = TASK_INTERRUPTIBLE (čakanie na I/O alebo zámky)
# Príklad výstupu:
# Tracing off-CPU time (us) of PID 12345... Ctrl-C to end.
#
# [30 sekundový trace]
#
# waker: mysql-server
# runtime.futexsleep
# runtime.notesleep
# runtime.mPark
# ...
# database/sql.(*DB).Query
# main.handleRequest
# - 5234521 us (5.2 sekundy celkového čakania na MySQL)
Bežné Vzory
Mutex Contention
# Nájdi futex čakania (mutex/condition variable)
/usr/share/bcc/tools/offcputime -p 12345 -K 30 2>&1 | grep futex
# Príklad výstupu:
# runtime.futexsleep
# sync.(*Mutex).Lock
# main.(*Cache).Get <- Contention tu!
# main.handleRequest
# 12345678 us
// Problematický kód (nájdený eBPF):
type Cache struct {
mu sync.Mutex // Jeden zámok pre celú cache
data map[string][]byte
}
func (c *Cache) Get(key string) []byte {
c.mu.Lock() // Všetky requesty sa serializujú tu
defer c.mu.Unlock()
return c.data[key]
}
// Riešenie: Shardované zámky
type ShardedCache struct {
shards [256]struct {
mu sync.RWMutex
data map[string][]byte
}
}
func (c *ShardedCache) Get(key string) []byte {
shard := &c.shards[hash(key)%256]
shard.mu.RLock() // Read zámok, menej contention
defer shard.mu.RUnlock()
return shard.data[key]
}
Vyčerpanie Database Connection Pool
# Nájdi čakania na sieťových socketoch
/usr/share/bcc/tools/offcputime -p 12345 30 | grep -E "epoll|read|write"
# Ak vidíš dlhé čakania v databázovom driveri:
# net.(*netFD).Read
# database/sql.(*DB).Query
# 25000000 us <- Čaká 25s na DB connection!
File I/O Blokuje
# Sleduj file system čakania
/usr/share/bcc/tools/filetop -p 12345 30
# Alebo s offcputime
/usr/share/bcc/tools/offcputime -p 12345 30 | grep -E "read|write|fsync"
# Bežný problém: synchrónne zápisy do súborov
# os.(*File).Write
# main.logToFile
# 5000000 us <- 5s blokovaný na file I/O
Pokročilé: Profiluj Špecifické Funkcie
Použitie bpftrace
# Nainštaluj bpftrace
apt-get install bpftrace
# Sleduj čas strávený v špecifickej funkcii
bpftrace -e '
uprobe:/path/to/myapp:main.slowFunction {
@start[tid] = nsecs;
}
uretprobe:/path/to/myapp:main.slowFunction /@start[tid]/ {
@latency = hist(nsecs - @start[tid]);
delete(@start[tid]);
}
'
# Výstup: histogram času vykonávania funkcie
Sleduj Lock Contention
# Sleduj futex contention
bpftrace -e '
tracepoint:syscalls:sys_enter_futex {
@start[tid] = nsecs;
}
tracepoint:syscalls:sys_exit_futex /@start[tid]/ {
$dur = nsecs - @start[tid];
if ($dur > 1000000) { // > 1ms
@slow[ustack, $dur / 1000000] = count();
}
delete(@start[tid]);
}
'
Integrácia Continuous Profiling
Parca Integrácia
# Parca agent config pre off-CPU profiling
apiVersion: v1
kind: ConfigMap
metadata:
name: parca-agent-config
data:
parca-agent.yaml: |
profiling:
cpu: true
offcpu: true # Povoľ off-CPU profiling
offcpu_threshold_ms: 10 # Zachyť čakania > 10ms
Pyroscope Integrácia
// Povoľ off-CPU profiling v Pyroscope
import "github.com/grafana/pyroscope-go"
pyroscope.Start(pyroscope.Config{
ApplicationName: "myapp",
ServerAddress: "http://pyroscope:4040",
ProfileTypes: []pyroscope.ProfileType{
pyroscope.ProfileCPU,
pyroscope.ProfileBlockProfile, // Blocking/mutex
pyroscope.ProfileGoroutineProfile,
},
})
Checklist
## Off-CPU Analýza
### Investigácia
- [ ] Skontroluj či CPU využitie << latencia naznačuje off-CPU čas
- [ ] Spusti offcputime na 30-60 sekúnd
- [ ] Vygeneruj flame graph pre vizualizáciu
- [ ] Identifikuj top off-CPU stacky
### Bežné Príčiny
- [ ] Mutex contention (futex čakania)
- [ ] Vyčerpanie databázového connection poolu
- [ ] Synchrónne file I/O
- [ ] Network I/O čakania
- [ ] Channel blocking v Go
### Riešenia
- [ ] Sharduj zámky pre zníženie contention
- [ ] Zväčši veľkosť connection poolu
- [ ] Použi async I/O kde možné
- [ ] Pridaj buffering pre zníženie syscallov
### Continuous Monitoring
- [ ] Povoľ off-CPU profiling v Parca/Pyroscope
- [ ] Alert na vysoký off-CPU pomer
- [ ] Dashboard ukazujúci CPU vs wall time
Záver
Keď CPU je nízke ale latencia vysoká, použi eBPF:
- Štandardné profilery nechytajú čakacie časy - vidia len CPU
- offcputime odhaľuje úplný obraz - zámky, I/O, všetko
- Flame graphy to urobia vizuálne - okamžite identifikuj bottlenecky
- Continuous profiling zachytí regresie - skôr než si všimnú používatelia
Tvoja aplikácia trávi viac času čakaním ako prácou. Zisti na čo čaká.
Súvisiace články
- K8s CPU Throttling Pitva - CPU performance
- Prometheus Kardinalita Explózia - Observabilita
Súvisiace články
eBPF Run-Queue Latency: Hľadanie Off-CPU Bottlenecku
CPU využitie je nízke ale requesty sú pomalé. Skrytý vinník: čas strávený čakaním v scheduler run-queue, neviditeľný pre tradičné profilery ale viditeľný s eBPF off-CPU analýzou.
tcpdump vidí SYN, ale služba timeoutuje: pasca listen backlogu
Klienti timeoutujú, tcpdump ukazuje SYN (niekedy aj SYN-ACK), ale aplikácia nič neloguje. Častý vinník: Linux listen/accept fronty, ktoré sa pri load-e alebo CPU starvation preplnia.
Prometheus WAL replay peklo: pomalý štart a chýbajúce alerty
Keď Prometheus štartuje desiatky minút, často je vinník WAL replay. Ako to dokázať z logov a disku, bezpečne sa zotaviť a predísť blind spotom.
Go p99 Latency Špičky: Vnorené context.WithTimeout Timer Búrky
Periodické latency špičky ktoré vyzerajú ako network jitter. Skutočná príčina: vnorené timeouty vytvárajú tisíce timerov ktoré zaťažujú Go runtime timer heap a spúšťajú GC scanning.
Citujte tento článok
Ak na článok odkazujete, pridajte pôvodnú URL a uveďte autora.