markdown
Nastroje-a-diagnostika.md
markdown
# 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. ```mermaidflowchart 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ň.** ```bashps -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 | headpidstat 1 5perf top # Je pomalá paměť?free -h # čti available, ne freegrep -E 'MemAvailable|Dirty|SwapCached' /proc/meminfocat /proc/1234/smaps_rollup # Pss, ne RSSslabtop -o # když paměť drží jádrodmesg -T | grep -i 'out of memory' # Je pomalý disk?iostat -xz 1 # await a aqu-sz, ne %utiliotop -o # chce rootcat /proc/pressure/io # Co dělá tenhle proces?cat /proc/1234/statuscat /proc/1234/wchan; ls -l /proc/1234/fdstrace -c -p 1234 # ukončit Ctrl+Cgdb -p 1234 -batch -ex 'thread apply all bt' # Kdo mi zaplnil disk?df -h; df -i # bajty i i-uzlydu -sh /var/* | sort -h | taillsof +L1 # Proč to spadlo?journalctl -p err -b -1 # minulý startdmesg -T -l err,warnss -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