# Nástroje a diagnostika systému

Server je pomalý. Devět lidí z deseti pustí `top`, podívá se na první řádek a řekne buď „procesor je vytížený", nebo „procesor není vytížený". Tím diagnostika skončila.

Jenže `top` odpovídá na jednu ze čtyř otázek, které je potřeba položit, a zbylé tři jsou v praxi častější příčinou. Když se procesor nudí a stroj přesto stojí, čeká se na disk, došla paměť, nebo se čeká na odpověď od někoho jiného.

Tahle stránka je rozcestník a tahák. Každý nástroj tu má tři věty a odkaz na stránku, kde je vysvětlené, co ta čísla znamenají. Mechanismy tu nevysvětluju znovu - ty mají [správa paměti](Sprava-pameti), [vstup a výstup](Vstup-a-vystup) a [plánování procesů](Planovani-procesu). Předpokládá to [procesy](Procesy), protože bez stavů `R`, `S`, `D` a `Z` je polovina výstupů nečitelná. Prostředí je Debian 13, jádro 6.12, osm jader, 16 GB RAM, NVMe disk.

## Čtyři zdroje a nic jiného

Tohle je nejdůležitější věc na celé stránce: **každý výkonnostní problém je nedostatek jednoho ze čtyř zdrojů - procesor, paměť, disk, síť.** Diagnostika není hledání viníka. Je to vyloučení tří ze čtyř.

Postup je vždycky stejný a liší se jen nástroje: celkový obraz, určení zdroje, konkrétní proces, a nakonec co ten proces dělá. Kdo přeskočí první krok, hledá vysvětlení pro číslo, které nikdy nezměřil.

```mermaid
flowchart TD
    A["Stroj je pomalý"] --> B{"vmstat: je us plus sy blízko 100?"}
    B -->|ano| C["Procesor: ps --sort=-pcpu, pidstat, perf top"]
    B -->|ne| D{"Je wa vysoké nebo b trvale nenulové?"}
    D -->|ano| E["Disk: iostat -xz 1, iotop -o"]
    D -->|ne| F{"Je si nebo so nenulové, je available malé?"}
    F -->|ano| G["Paměť: free -h, smaps_rollup, dmesg kvůli OOM"]
    F -->|ne| H{"Visí proces na soketu nebo na volání?"}
    H -->|ano| I["Protistrana: ss -tulpn, strace -p"]
    H -->|ne| J["Není to zdroj: zámek nebo čekání ve vlastním kódu"]
```

Každá větev má níž vlastní sekci. Poslední uzel je ten nepříjemný: stroj má všechno a přesto stojí, protože si program překáží sám.

## Celkový obraz: `uptime` a `vmstat 1`

### Zátěž (load average) neměří procesor

„Zátěž 20 na osmi jádrech znamená přetížený procesor." Tuhle větu najdeš ve stovkách návodů a **na Linuxu je nesprávná**.

Linux do zátěže počítá procesy ve stavu `R` **a zároveň ve stavu `D`** - nepřerušitelný spánek, typicky čekání na disk nebo na síťové úložiště. Ostatní Unixy počítají jen `R`. Dvacet procesů zaseknutých na nedostupném NFS tedy udělá zátěž 20 při procesoru nečinném na 100 %.

**Zátěž ber jako otázku „stalo se něco?", nikdy jako odpověď.** Tři čísla z `uptime` jsou navíc tlumené průměry za 1, 5 a 15 minut, takže krátkou špičku rozpustí do bezvýznamné hodnoty.

### Sloupce `vmstat 1`

`vmstat 1` tiskne řádek za sekundu a je to nejlevnější způsob, jak během deseti sekund vyloučit tři zdroje ze čtyř. **První řádek zahoď**, je to průměr od startu stroje.

| Sloupec | Co znamená | Kdy se lekni |
|---|---|---|
| `r` | procesy připravené běžet | trvale víc než jader |
| `b` | procesy v nepřerušitelném spánku | trvale nenulové |
| `si` | stránky načítané ze swapu | nenulové delší dobu |
| `so` | stránky odkládané do swapu | nenulové delší dobu |
| `bi` | čtení z blokových zařízení | vysoké spolu s `wa` |
| `bo` | zápis na bloková zařízení | vysoké spolu s `wa` |
| `in` | přerušení za sekundu | skok o řád proti klidu |
| `cs` | přepnutí kontextu za sekundu | statisíce při malém `r` |
| `us` | čas v uživatelském režimu | trvale blízko 100 |
| `sy` | čas v jádře | nad 30 |
| `id` | nečinnost | vysoké při stížnosti na pomalost |
| `wa` | nečinnost s čekáním na I/O | nad 10 |

Vysoké `cs` při malém `r` znamená, že si procesy předávají procesor rychleji, než na něm stihnou něco udělat - cena je na [přepínání kontextu](Prepinani-kontextu). Ve virtuálním stroji sleduj navíc `st`, tedy čas, který ti vzal hypervizor.

## Procesy: kdo z toho žere

### `ps aux` versus `ps -ef` versus vlastní formát

`ps aux` je BSD syntaxe, `ps -ef` syntaxe podle POSIXu. Stejná data, jiné sloupce. **Nauč se vlastní formát a obě zapomeň.**

```bash
ps -eo pid,ppid,stat,pcpu,rss,wchan,comm --sort=-pcpu | head -20
```

`wchan` řekne, ve které funkci jádra proces spí, a u stavu `D` je to nejcennější údaj na řádku. Pozor na `pcpu`: **`ps` počítá průměr za celý život procesu**, ne aktuální vytížení. Proces, který týden spal a teď žere jádro naplno, tam bude mít skoro nulu.

### `top` a co v něm mačkat

Klávesa `1` rozpadne souhrn na jednotlivá jádra. To je jediný způsob, jak v `top` poznat jednovláknový program s jedním jádrem na 100 % - v souhrnu je to při osmi jádrech 12,5 % a vypadá to nevinně.

Klávesa `H` přepne na vlákna, `M` řadí podle paměti, `P` zpátky podle procesoru. `htop` umí totéž čitelněji a přes `F5` ukáže strom.

`pidstat 1` je `vmstat` po procesech a jediný z téhle skupiny dává výstup, který se dá uložit a porovnat později. `pstree -p` odpoví, kdo koho spustil. Priority a `nice` má [plánování procesů](Planovani-procesu).

## Paměť: dívej se na `available`

`free -h` vyvolá paniku pokaždé: „mám volných 1,6 GB z 16". Sloupec `buff/cache` ale není ztracená paměť, je to hlavně page cache a jádro ji vydá, jakmile ji někdo bude potřebovat.

**Jediné číslo, které z `free -h` čti, je `available`.** Je to odhad jádra, kolik se dá alokovat bez odkládání do swapu. Pochází z položky `MemAvailable` v `/proc/meminfo` (od jádra 3.14, rok 2014); předtím si to každý nástroj počítal sám a špatně.

Když paměť ubývá a nevíš komu, jdi po pořadí: `/proc/PID/smaps_rollup` a v něm `Pss` místo RSS (RSS připočte sdílené knihovny každému procesu zvlášť, takže součet přes procesy vyjde větší než celá RAM), `pmap -x PID` na rozpis mapování, `smem` na PSS napříč procesy a `slabtop`, když paměť nedrží procesy, ale struktury jádra. Swap, thrashing a OOM killer mají [výměnné algoritmy](Vymenne-algoritmy).

## Disk a vstup/výstup

`iostat -xz 1` je pro disk to, co `vmstat 1` pro celý stroj. `-x` zapne rozšířené sloupce, `-z` vynechá zařízení bez provozu - bez něj se utopíš v třiceti řádcích `loop0` až `loop29`. Čti `r_await` a `w_await` (milisekundy na požadavek včetně fronty) a `aqu-sz`.

`iotop -o` ukáže jen procesy, které právě čtou nebo zapisují. Chce root a doinstalovat, stejně jako `smem` a `ncdu`.

`df -h` a `df -i` jsou dvě různá „plno". Na souborovém systému s milionem malých souborů dojdou i-uzly dřív než bajty, zápis začne selhávat a `df -h` u toho hlásí volných 40 % - viz [souborový systém](Souborovy-system).

### Kdo mi zaplnil disk

`du -sh * | sort -h` na podezřelém adresáři, `ncdu /var`, když se chceš proklikat. `du` ale nevidí to nejčastější překvapení.

**`lsof +L1` vypíše soubory, které někdo drží otevřené, ale už nemají odkaz v adresáři.** Smažeš `access.log`, `du` ho přestane počítat a `df` se nezmění ani o bajt, protože nginx pořád drží deskriptor. Místo se uvolní až po `systemctl reload nginx`.

## Síť potud, kde končí systém

Síť není téma téhle wiki, ale zjistit, který proces drží port, k diagnostice systému patří. Na to je jediný příkaz: `ss -tulpn` - `-t` TCP, `-u` UDP, `-l` naslouchající sokety, `-p` proces, `-n` čísla místo jmen služeb.

**`netstat` už nepoužívej.** Balík net-tools je prakticky bez vývoje od začátku století a `netstat` získává data parsováním textu z `/proc/net/tcp`, kdežto `ss` se ptá jádra přes netlink a při desetitisících spojení je řádově rychlejší. V návodech zůstává setrvačností a na čerstvém Debianu často není ani nainstalovaný. Totéž platí pro `ifconfig` proti `ip a`.

`ss -s` dá souhrn v pěti řádcích. Když počet spojení ve stavu `timewait` roste do desetitisíců, problém není ve stroji, ale v tom, jak aplikace otevírá a zavírá spojení.

## Co ten proces vlastně dělá

### `strace` a jeho cena

`strace -p PID` vypisuje systémová volání běžícího procesu. `strace -c -p PID` místo výpisu sečte statistiku a po `Ctrl+C` vytiskne tabulku „které volání, kolikrát, kolik času, kolik chyb" - to je devadesát procent případů, kdy `strace` opravdu potřebuješ. `-f` přidá potomky, `ltrace` dělá totéž pro volání knihoven.

**Varování: `strace` program zpomalí řádově.** Každé zachycené volání znamená dvě zastavení procesu přes `ptrace` a dvě přepnutí do sledujícího procesu a zpět. Program se statisíci volání za sekundu se zpomalí desetinásobně i víc, takže na produkci pod zátěží pouštěj jen `-c` a na pár sekund. Co se v tom výpisu objevuje, rozebírají [systémová volání](Systemova-volani).

### Když `strace` mlčí

Prázdný výstup není chyba nástroje. Znamená, že proces visí uvnitř jednoho volání a nic nového nezačíná.

Pak se ptej jádra přímo. `cat /proc/PID/wchan` vypíše funkci, ve které proces spí (`ep_poll` je čekání na událost, `futex_wait` čekání na zámek), `sudo cat /proc/PID/stack` ukáže celý zásobník v jádře.

Na uživatelskou stranu je `gdb -p PID` a v něm `thread apply all bt`, tedy zásobníky všech vláken naráz. Bez ladicích symbolů dostaneš jen adresy, takže si `gdb` i symboly doinstaluj dřív, než je budeš potřebovat. Po dobu připojení je proces zastavený.

## `/proc` je zdroj pravdy, zbytek jsou formátovače

Žádný z nástrojů výš nemá přístup k ničemu zvláštnímu. Všechny jen čtou textové soubory z `/proc` a hezky je formátují - `strace -c ps aux` ti to ukáže během vteřiny.

| Soubor | Co v něm je |
|---|---|
| `/proc/cpuinfo` | jádra, model, frekvence, příznaky instrukcí |
| `/proc/meminfo` | paměťová statistika včetně `MemAvailable` |
| `/proc/interrupts` | počty přerušení po zdrojích a po jádrech |
| `/proc/mounts` | co je skutečně namontované a s jakými volbami |
| `/proc/pressure/*` | tlak na procesor, paměť a vstup/výstup |
| `/proc/PID/status` | stav, RSS, vlákna, přepnutí kontextu |
| `/proc/PID/limits` | limity procesu tak, jak platí teď |
| `/proc/PID/fd/` | otevřené deskriptory jako symbolické odkazy |
| `/proc/PID/smaps_rollup` | souhrn všech mapování, hlavně `Pss` |

Důsledek je větší, než vypadá: v kontejneru s pěti binárkami, kde není `ps` ani `top`, odladíš skoro všechno pomocí `cat`. A `/proc/PID/limits` je jediné místo se skutečnými limity běžícího procesu - `ulimit -n` ve tvém shellu o cizím procesu neříká nic. Proč to vypadá jako soubory, vysvětluje [VFS a montování](VFS-a-montovani).

## Logy: co systém říká sám o sobě

`journalctl -b` je vše od posledního startu, `journalctl -f -u nginx` živý výpis jedné služby a `journalctl -p err -b -1` chyby z **minulého** startu. K tomu poslednímu se dostaneš vždycky až po pádu, tak si ho zapamatuj teď. Když `-b -1` nic nevrátí, žurnál se nedrží mezi starty - založ `/var/log/journal`.

`dmesg -T -l err,warn` je kruhová vyrovnávací paměť jádra profiltrovaná na chyby a varování. Tady najdeš OOM killer, chyby disku i odpojené USB. Přepínač `-T` převede časy na datum, ale po uspání stroje jsou posunuté, protože se dopočítávají z uptime. Co se děje dřív, než začne psát systemd, je na [startu systému](Start-systemu).

## Co stojí za naučení navíc

### `perf`

`perf top` je živý profil celého stroje po funkcích, včetně funkcí jádra. Odpoví na otázku „v čem se ten čas tráví", na kterou `top` odpoví jen „v tomhle procesu".

`perf stat ./program` dá instrukce, cykly, IPC a výpadky cache, `perf record -g ./program` a `perf report` záznam k pozdějšímu prohlížení. Chce doinstalovat a mít `kernel.perf_event_paranoid` dost nízko.

### PSI: číslo, které je lepší než zátěž

Soubory `/proc/pressure/cpu`, `io` a `memory` existují od jádra 4.20 (rok 2018) a řeší přesně to, co je na zátěži špatně.

Řádek `some avg10=8.52 avg60=2.37 avg300=0.67` znamená, že za posledních deset sekund byla aspoň jedna úloha 8,52 % času zablokovaná čekáním na ten zdroj. Řádek `full` totéž pro stav, kdy čekaly všechny naráz.

**PSI měří ztracený čas, ne počet procesů, a rozlišuje zdroj.** Nula v `/proc/pressure/io` je důkaz, že disk problém není, a to zátěž neřekne nikdy. Na některých systémech je potřeba přidat `psi=1` mezi parametry jádra. Vlastní sadu těchhle souborů má každá cgroup, což je z hlediska [kontejnerů](Virtualizace-a-kontejnery) to nejužitečnější, co v posledních letech přibylo.

### eBPF a `bpftrace`

Nástroje nad eBPF běží v jádře, nezastavují sledovaný proces jako `ptrace` a jejich režie se počítá v procentech, ne v násobcích.

**Nauč se první `execsnoop`.** Vypíše každý spuštěný proces včetně těch, které žijí dvacet milisekund a v `top` je nikdy neuvidíš - a přesně ty bývají příčinou záhadné zátěže z cronu. Pak `opensnoop` a `biolatency`, který dá histogram latence disku místo průměru.

## Kde tyhle nástroje přestávají platit

**Kontejner vidí jinou pravdu.** `/proc/meminfo` uvnitř kontejneru hlásí paměť hostitele, ne limit cgroup, takže `free -h` tam lže a `nproc` může hlásit osm jader při kvótě půl jádra. Skutečné hodnoty jsou v `/sys/fs/cgroup/`.

**`%util` z `iostat` na NVMe nic neznamená.** Ten sloupec říká, kolik procent času mělo zařízení aspoň jeden požadavek. U plotnového disku s jednou hlavou to byla saturace, u NVMe s desítkami paralelních front může být 100 % při zlomku skutečného výkonu.

**Zálohu, kterou nikdo nezkusil obnovit, nemáš.** GitLab, 31. ledna 2017: administrátor při řešení zahlcené databáze smazal datový adresář PostgreSQL na špatném stroji a pak zjistil, že žádný z pěti nastavených způsobů zálohování a replikace nefunguje. Obnovilo se to ze šest hodin staré kopie, která vznikla omylem. **Všech pět nástrojů přitom hlásilo, že je všechno v pořádku.**

## Příznak a co za ním hledat

| Příznak | Kde je problém |
|---|---|
| zátěž vysoká, `id` blízko 100 | procesy ve stavu `D`, čekání na disk |
| `sy` výrazně vyšší než `us` | příliš mnoho volání, ověř `strace -c` |
| `cs` ve statisících, `r` malé | příliš mnoho vláken, přepínání sežere víc než práce |
| `si` a `so` trvale nenulové | došla paměť - [výměnné algoritmy](Vymenne-algoritmy) |
| `df` hlásí plno, `du` ne | smazaný soubor drží proces, `lsof +L1` |
| `df` má místo, zápis selže | došly i-uzly, ověř `df -i` |
| paměť procesu roste a nevrací se | únik nebo neomezená cache, `smaps_rollup` |
| proces nereaguje, `strace` nic nepíše | visí uvnitř volání, čti `/proc/PID/wchan` |
| služba spadla a v logu nic není | zabil ji OOM killer, hledej v `dmesg -T` |
| jedno jádro na 100 %, ostatní nic | jednovláknový program, v `top` klávesa `1` |

## Tahák

```bash
# Je pomalý procesor?
uptime                                     # zátěž jen jako otázka, ne odpověď
vmstat 1 10                                # r, us, sy, wa; první řádek zahoď
ps -eo pid,stat,pcpu,rss,wchan,comm --sort=-pcpu | head
pidstat 1 5
perf top

# Je pomalá paměť?
free -h                                    # čti available, ne free
grep -E 'MemAvailable|Dirty|SwapCached' /proc/meminfo
cat /proc/1234/smaps_rollup                # Pss, ne RSS
slabtop -o                                 # když paměť drží jádro
dmesg -T | grep -i 'out of memory'

# Je pomalý disk?
iostat -xz 1                               # await a aqu-sz, ne %util
iotop -o                                   # chce root
cat /proc/pressure/io

# Co dělá tenhle proces?
cat /proc/1234/status
cat /proc/1234/wchan; ls -l /proc/1234/fd
strace -c -p 1234                          # ukončit Ctrl+C
gdb -p 1234 -batch -ex 'thread apply all bt'

# Kdo mi zaplnil disk?
df -h; df -i                               # bajty i i-uzly
du -sh /var/* | sort -h | tail
lsof +L1

# Proč to spadlo?
journalctl -p err -b -1                    # minulý start
dmesg -T -l err,warn
ss -tulpn                                  # kdo drží který port
```

## Praktická pravidla

**Měň jednu věc naráz.** Když změníš tři a začne to fungovat, nevíš nic.

**Změř to před a po.** Bez čísla z doby, kdy to bylo špatně, nemáš důkaz, jen dojem.

**Nedůvěřuj průměru za pět minut, když tě zajímá špička.** Dvousekundový zásek se v něm rozpustí na desetinu procenta.

**Nástroj, který ti řekne, že je všechno v pořádku, nic nedokazuje.** Dokazuje jen to, že se dívá jinam, než kde je problém.

**Problém, který nejde reprodukovat, jsi nevyřešil.** Bez reprodukce nepoznáš opravu od náhody.

## Kam dál

- **[Procesy](Procesy)** - stavy `R`, `S`, `D` a `Z`, bez kterých je půlka výstupů nečitelná
- **[Plánování procesů](Planovani-procesu)** - proč připravený proces nemusí běžet
- **[Správa paměti](Sprava-pameti)** - co je RSS, co PSS a proč je součet přes procesy nesmysl
- **[Výměnné algoritmy](Vymenne-algoritmy)** - swap, thrashing a jak vybírá OOM killer
- **[Vstup a výstup](Vstup-a-vystup)** - page cache a fronty, které `iostat` měří
- **[Systémová volání](Systemova-volani)** - co přesně vypisuje `strace`
- **[VFS a montování](VFS-a-montovani)** - proč `/proc` vypadá jako soubory a není jimi
