Späť na blog

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:

  1. Štandardné profilery nechytajú čakacie časy - vidia len CPU
  2. offcputime odhaľuje úplný obraz - zámky, I/O, všetko
  3. Flame graphy to urobia vizuálne - okamžite identifikuj bottlenecky
  4. 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

Súvisiace články

Citujte tento článok

Ak na článok odkazujete, pridajte pôvodnú URL a uveďte autora.

Michal Drozd. "eBPF Off-CPU Analýza: Nájdenie Latencie Ktorú Metriky Nevidia". https://www.michal-drozd.com/sk/blog/ebpf-off-cpu-debugging/ (Publikované 7. septembra 2025).