60. perf, top, vmstat, iostat и другие инструменты Linux
Введение: От локального дебага к production-наблюдаемости
В экосистемах виртуальных машин (Java, C#) или интерпретируемых языков (PHP, Python, Ruby) инженеры исторически привыкли опираться на тяжелые встроенные агенты телеметрии и виртуальные счетчики: VisualVM, Xdebug, jstat, dotnet-counters или async-profiler. В мире Go философия принципиально иная — Minimalism + Mechanical Sympathy. Язык Go не пытается переизобрести операционную систему: скомпилированный бинарник взаимодействует напрямую с ядром Linux, а рантайм делегирует учет системных ресурсов стандартным механизмам ядра.
Для Senior и Lead бэкенд-разработчика умение читать вывод классических утилит top, vmstat, iostat и низкоуровневого профилировщика perf — это базовый навык «механического сочувствия» к серверу. Когда в часы пиковой нагрузки 99-й процентиль задержки (p99 latency) внезапно взлетает с 5 миллисекунд до 3 секунд, прикладные метрики Prometheus констатируют пожар, но бессильны объяснить его физическую причину.
Вы должны уметь с первого взгляда на терминал отличить:
- паузу сборщика мусора (GC pause) от катастрофического вытеснения страниц в дисковый swap (
si/so); - конкуренцию за мьютекс (
sync.Mutex) в пространстве пользователя от яростных переключений контекста в планировщике ядра Linux; - асинхронный неблокирующий сетевой ввод-вывод (netpoller) от скрытых блокирующих системных вызовов в синхронном режиме I/O.
flowchart TD
classDef process fill:#1e293b,stroke:#38bdf8,stroke-width:1px,color:#f8fafc
classDef success fill:#064e3b,stroke:#34d399,stroke-width:1px,color:#f8fafc
classDef error fill:#7f1d1d,stroke:#f87171,stroke-width:1px,color:#f8fafc
classDef warning fill:#78350f,stroke:#fbbf24,stroke-width:1px,color:#f8fafc
classDef entry fill:#1e3a5f,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
classDef aux fill:#334155,stroke:#64748b,stroke-width:1px,color:#f8fafc
classDef system fill:#312e81,stroke:#a78bfa,stroke-width:1px,color:#f8fafc
classDef data fill:#132d29,stroke:#2dd4bf,stroke-width:1px,color:#f8fafc
classDef network fill:#881337,stroke:#fb7185,stroke-width:1px,color:#f8fafc
classDef accent fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#f8fafc
subgraph UserSpace ["Уровень приложения и Go Runtime (User Space)"]
GoRoutines["Горутины (G) и пул потоков (M)"]
PprofTool["pprof: Профилирование heap, mutex, goroutines"]:::system
AppMetrics["Prometheus / OpenTelemetry: RPS, p99 Latency"]:::process
end
subgraph KernelSpace ["Уровень ядра Linux (Kernel Space)"]
VFS_IO["Подсистема VFS и Page Cache (iostat)"]
MemVM["Виртуальная память и свопинг (vmstat)"]
SchedCore["Планировщик и треды task_struct (top / htop)"]
PerfEvents["Подсистема perf_events ядра"]:::process
end
subgraph HardwareSpace ["Аппаратный уровень (CPU / RAM / NVMe)"]
PMU["PMU: Hardware Performance Counters (perf)"]
StorageDev["NVMe / SSD Накопитель (%util, await)"]
DRAM["Оперативная память (RAM Cache Misses)"]
end
GoRoutines --> SchedCore
GoRoutines --> VFS_IO
PprofTool -.->|"Дополняет"| PerfEvents
SchedCore --> PMU
VFS_IO --> StorageDev
MemVM --> DRAM
В этом разделе мы глубоко разберем стандартный арсенал Linux-наблюдаемости, внутреннее устройство ядра, стоящее за каждой метрикой, и специфику интерпретации этих данных для высоконагруженных сервисов на Go.
top / htop / btop: Глаза поверх процесса
Утилиты семейства top (htop, btop) — это первая линия визуальной диагностики, предоставляющая динамическую картину состояния физических ядер процессора, оперативной памяти и запущенных процессов.
В традиционных языках с моделью 1:1 (где одному потоку приложения строго соответствует один поток ОС, как в Java до прихода Virtual Threads) утилита вроде jstack легко отображает каждый тред. Однако язык Go использует модель планировщика M:N: миллионы легковесных горутин (G) мультиплексируются средой выполнения на сравнительно небольшое число потоков операционной системы (M). Поэтому утилиты top видят не горутины, а исключительно системные потоки ядра Linux (task_struct).
Под капотом
Утилита top не вторгается в память процессов и не использует отладчик ptrace. Она периодически считывает псевдофайлы ядра в виртуальной файловой системе /proc:
/proc/[pid]/stat— время процессора, проведенное процессом в пространстве пользователя (utime) и ядра (stime), приоритет nice, состояние (R, S, D, Z)./proc/[pid]/status— объем виртуальной (VmSize) и резидентной физической памяти (VmRSS), лимиты дескрипторов, количество потоков (Threads).- Системные вызовы
getrusage()и вызовsysconf()для расчета тиков процессорного таймера (clock ticks,USER_HZ).
Go-специфика
# Показать потоки (M) конкретного Go-процесса в реальном времени
top -p $(pgrep myapp) -H -o %CPU
# Отфильтровать вывод только по пользовательскому времени (без системных вызовов)
top -p $(pgrep myapp) -H -o %us
vmstat: Пульс виртуальной памяти и контекстных переключений
Утилита vmstat (Virtual Memory Statistics) — пожалуй, самый компактный и информативный инструмент для мгновенной оценки общего здоровья Linux-сервера. Она объединяет в одной строке статистику очереди планировщика (runqueue), активности памяти, подкачки (swapping), блочного ввода-вывода и аппаратных прерываний.
Ключевые колонки и их интерпретация для Go
Запуск с интервалом обновления в 1 секунду:
vmstat 1 10
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
4 0 0 1024340 45212 4312890 0 0 2 34 540 1200 45 12 42 1 0
| Колонка | Описание подсистемы ядра | Интерпретация для архитектуры Go-приложения |
|---|---|---|
r |
Runnable processes (длина очереди планировщика CFS/EEVDF). | Количество тредов ОС, готовых к выполнению прямо сейчас. В Go горутины переходят из Gwaiting в состояние runnable (состояние block -> runnable). Если значение r устойчиво превышает значение GOMAXPROCS, на сервере возникла жестокая конкуренция за CPU (CPU contention): процессоры перегружены. |
b |
Blocked processes (потоки в состоянии Uninterruptible Sleep D). |
Потоки ОС, заблокированные в ожидании дискового ввода-вывода (IO), захвата тяжелых блокировок мьютексов в ядре или медленных синхронных каналов. |
si / so |
Swap in / Swap out (подкачка страниц памяти с диска). | Критический маркер аварии! Если значение so > 0, операционная система выталкивает страницы оперативной памяти на диск. Сборщик мусора Go (GC) периодически обходит всю кучу (heap). Если куча находится в swap, каждый цикл GC приводит к миллионам обращений к диску, вызывая catastrophic latency spikes (секундные зависания сервиса). |
cs |
Context switches (переключения контекста процессора). | Частота переключения регистров CPU между потоками ядра. Высокий показатель cs указывает на частую смену системных тредов M либо на постоянный переход процессора между Ring 3 (user space) и Ring 0 (ядро). |
wa |
IO wait (простой процессора в ожидании ответа диска). | Доля времени CPU, когда ядра простаивали, потому что все готовые потоки ждали завершения дисковых операций. В Go скрывает блокирующие системные вызовы read/write. |
iostat: Пульс дисковой подсистемы
Современные бэкенды на Go активно взаимодействуют с блочными накопителями: запись журналов (structured logging), локальное кэширование, загрузка пользовательских файлов, работа с встроенными базами (Badger, BoltDB) и классическими реляционными СУБД (транзакционные журналы WAL, таблицы и индексы).
Утилита iostat (из пакета sysstat) детально визуализирует производительность дисковых контроллеров и виртуальных устройств.
# Расширенная статистика по дискам с шагом в 1 секунду (5 итераций)
iostat -x 1 5
Ключевые метрики
Device r/s w/s rkB/s wkB/s await %util
nvme0n1 12.00 450.00 128.00 18450.00 0.85 82.40
%util(Утилизация устройства): Процент времени, в течение которого контроллер устройства обрабатывал хотя бы один активный запрос. Значение свыше80%означает, что накопитель близок к насыщению своей пропускной способности.await(Среднее время ожидания, мс): Полное время выполнения одного запроса на чтение/запись. Включает как время нахождения запроса в системной очереди диспетчера ввода-вывода (I/O scheduler), так и физическое исполнение контроллером SSD/NVMe.r/sиw/s: Количество завершенных операций чтения и записи в секунду (IOPS).rkB/sиwkB/s: Объем прочитанных и записанных данных в килобайтах в секунду (пропускная способность / throughput).
Go-специфика
По умолчанию стандартная библиотека Go использует Page Cache ядра Linux для всех операций с файлами (os.File). Это означает, что при записи данные копируются из пользовательского буфера []byte в оперативную память ядра (страничный кэш). Системный вызов завершается практически мгновенно, но позже ядро сбрасывает «грязные» страницы на диск через фоновые потоки flusher/kworker.
flowchart LR
classDef process fill:#1e293b,stroke:#38bdf8,stroke-width:1px,color:#f8fafc
classDef success fill:#064e3b,stroke:#34d399,stroke-width:1px,color:#f8fafc
classDef error fill:#7f1d1d,stroke:#f87171,stroke-width:1px,color:#f8fafc
classDef warning fill:#78350f,stroke:#fbbf24,stroke-width:1px,color:#f8fafc
classDef entry fill:#1e3a5f,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
classDef aux fill:#334155,stroke:#64748b,stroke-width:1px,color:#f8fafc
classDef system fill:#312e81,stroke:#a78bfa,stroke-width:1px,color:#f8fafc
classDef data fill:#132d29,stroke:#2dd4bf,stroke-width:1px,color:#f8fafc
classDef network fill:#881337,stroke:#fb7185,stroke-width:1px,color:#f8fafc
classDef accent fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#f8fafc
subgraph GoApp ["Go-приложение"]
Buffer["Буфер памяти ([]byte)"]
end
subgraph KernelVFS ["Ядро Linux: Page Cache"]
PageCache["Страничный кэш (Dirty Pages)"]
IOScheduler["Планировщик ввода-вывода (I/O Queue)"]
end
subgraph StorageHardware ["Накопитель (NVMe / SSD)"]
DiskController["Контроллер (%util, await)"]
end
Buffer -->|"os.File.Write()"| PageCache
PageCache -.->|"Фоновый сброс (writeback)"| IOScheduler
IOScheduler -->|"r/s, w/s, rkB/s, wkB/s"| DiskController
perf: Hardware counters и профилирование
Если стандартный профайлер Go pprof — это взгляд на программу «изнутри» (с точки зрения структур рантайма), то perf — это тяжелая артиллерия низкоуровневого анализа, позволяющая исследовать программу «снаружи», со стороны кремниевого кристалла и ядра Linux.
Инструмент perf опирается на подсистему ядра perf_events и аппаратные счетчики производительности процессора (PMU — Performance Monitoring Unit). Он позволяет с абсолютной точностью замерять: такты процессора, промахи мимо кэша L1/L2/L3 (Cache Misses), количество неверно предсказанных ветвлений (Branch Mispredictions), межъядерную инвалидацию строк кэша (False Sharing) и скрытые микропаузы на системных вызовах.
Как работает perf
- Конфигурация счетчиков: Ядро программирует регистры PMU процессора на генерацию прерывания при наступлении заданного числа аппаратных событий (например, каждые 100 000 выполненных инструкций или промахов кэша L3).
- Аппаратное прерывание: Процессор генерирует прерывание PMI (Performance Monitoring Interrupt), и ядро Linux перехватывает управление.
- Запись в кольцевой буфер: Ядро считывает текущий указатель команд (Instruction Pointer, RIP) и стек вызовов, записывая срез события в разделяемый кольцевой буфер (ring buffer).
- Анализ: Пользовательская утилита
perfсчитывает ring buffer и формирует детализированную статистическую картину выполнения кода.
flowchart TD
classDef process fill:#1e293b,stroke:#38bdf8,stroke-width:1px,color:#f8fafc
classDef success fill:#064e3b,stroke:#34d399,stroke-width:1px,color:#f8fafc
classDef error fill:#7f1d1d,stroke:#f87171,stroke-width:1px,color:#f8fafc
classDef warning fill:#78350f,stroke:#fbbf24,stroke-width:1px,color:#f8fafc
classDef entry fill:#1e3a5f,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
classDef aux fill:#334155,stroke:#64748b,stroke-width:1px,color:#f8fafc
classDef system fill:#312e81,stroke:#a78bfa,stroke-width:1px,color:#f8fafc
classDef data fill:#132d29,stroke:#2dd4bf,stroke-width:1px,color:#f8fafc
classDef network fill:#881337,stroke:#fb7185,stroke-width:1px,color:#f8fafc
classDef accent fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#f8fafc
subgraph Hardware ["Аппаратный процессор (CPU Core)"]
InstructionExec["Выполнение инструкций Go-бинарника"]:::process
PMUCounters["Счетчики PMU: Cache Misses, Cycles, Branches"]:::data
end
subgraph KernelRing ["Пространство ядра Linux"]
PMI_Interrupt["Прерывание PMI при переполнении счетчика"]:::process
RingBuffer["Кольцевой буфер perf_events в ОЗУ"]:::process
end
subgraph PerfCLI ["Пользовательские инструменты perf"]
PerfRecord["perf record -g -- ./myapp"]:::process
PerfReport["perf report / FlameGraph"]:::process
end
InstructionExec --> PMUCounters
PMUCounters -->|"Порог достигнут"| PMI_Interrupt
PMI_Interrupt -->|"Снимок регистров RIP, RSP"| RingBuffer
RingBuffer --> PerfRecord
PerfRecord --> PerfReport
Профилирование Go-приложения
# Запись профиля работы Go-приложения с частотой сэмплирования 99 Hz и раскруткой стеков
perf record -g -F 99 -- ./myapp
# Интерактивный отчет по самым горячим символам и динамическим библиотекам
perf report --sort=dso,symbol
# Построение векторного Flame Graph (используя утилиты Брендана Грегга)
perf script | stackcollapse-perf.pl | flamegraph.pl > flame.svg
Go-специфика: Как читать эти инструменты для бэкенда
Ниже представлена проверенная архитектурная схема расследования деградации производительности в высоконагруженных распределенных сервисах:
flowchart TD
classDef process fill:#1e293b,stroke:#38bdf8,stroke-width:1px,color:#f8fafc
classDef success fill:#064e3b,stroke:#34d399,stroke-width:1px,color:#f8fafc
classDef error fill:#7f1d1d,stroke:#f87171,stroke-width:1px,color:#f8fafc
classDef warning fill:#78350f,stroke:#fbbf24,stroke-width:1px,color:#f8fafc
classDef entry fill:#1e3a5f,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
classDef aux fill:#334155,stroke:#64748b,stroke-width:1px,color:#f8fafc
classDef system fill:#312e81,stroke:#a78bfa,stroke-width:1px,color:#f8fafc
classDef data fill:#132d29,stroke:#2dd4bf,stroke-width:1px,color:#f8fafc
classDef network fill:#881337,stroke:#fb7185,stroke-width:1px,color:#f8fafc
classDef accent fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#f8fafc
Incident["Рост задержек (p99 latency) / Отказы в обработке запросов"]:::entry --> RunVmstat["Запуск vmstat 1: анализ r, b, si, so, cs"]
RunVmstat --> CheckRunqueue{"Очередь r > GOMAXPROCS?"}
CheckRunqueue -->|"Да: CPU Contention"| PerfCPU["perf record -g -- ./myapp -> Flame Graph"]
PerfCPU --> OptimizeCPU["Оптимизация: sync.Pool, atomic, уменьшение аллокаций"]
CheckRunqueue -->|"Нет"| CheckBlocked{"Колонка b высока?"}
CheckBlocked -->|"Да: Ожидание I/O"| CheckDisk{"iostat -x: %util > 80%?"}
CheckDisk -->|"Да: Дисковый ботлнек"| DiskFix["Решение: bufio, асинхронный сброс, NVMe"]
CheckDisk -->|"Нет: Блокировка в сети/мьютексах"| MutexLock["strace -c / pprof -block / pprof -mutex"]
RunVmstat --> CheckSwap{"Колонка so > 0?"}
CheckSwap -->|"Да: Сервис вытесняется в Swap"| OOMFix["Катастрофа! Настройка GOMEMLIMIT, отключение swap"]
Практические сценарии
- Паузы сборщика мусора вызывают всплески латентности (Latency Spikes):
- Симптомы: В
topнаблюдаются резкие цикличные провалы%us, вvmstatкратковременно растет колонкаb, внешние HTTP-клиенты получают таймаут. - Проверка: Запуск сервиса с флагом
GODEBUG=gctrace=1 | grep gcwaitили снятие профиля аллокацийpprof -alloc_objects. - Лечение: Временная корректировка
GOGC=50, радикальное снижение аллокаций в горячих циклах через повторное использование буферов с помощью пулаsync.Pool, замена динамических срезов[]byteнаmmap.
- Симптомы: В
- Утечка горутин (Goroutine Leak):
- Симптомы: В
topпоказатель RSS (физическая память процесса) монотонно растет линейно вверх; со временем вvmstatначинает расти колонка подкачкиsi/so. - Проверка: Снятие дампов памяти
pprof -inuse_spaceили профилирование общего объема аллокацийpprof -alloc_space, а также проверка эндпоинта/debug/pprof/goroutine?debug=1. - Лечение: Поиск горутин, заблокированных на незакрытых каналах, зависших контекстах
context.Contextили сетевых клиентахnet/httpбез выставленных таймаутовTimeout.
- Симптомы: В
- Яростная конкуренция за процессор на мьютексах (Lock Contention):
- Симптомы: В
topфиксируется аномально высокий системный процент времени%sy, вvmstatчисло переключений контекстаcsдостигает сотен тысяч в секунду. - Проверка: Запуск
perf schedдля анализа переключений контекста планировщика или вызовperf c2cдля выявления ложного разделения кэш-линий (Last Cache Miss). - Лечение: Замена тяжелого эксклюзивного
sync.Mutexна разделяемыйsync.RWMutex, шардирование глобальных структур данных (sharded map), максимальное сокращение критических секций и переход на атомарные операцииatomic.
- Симптомы: В
Сравнение с другими языками
| Аспект наблюдаемости | Экосистема Java (JVM) | Экосистема .NET (CLR) | Язык Go |
|---|---|---|---|
| Мониторинг процессов | jcmd, VisualVM |
dotnet-counters |
Стандартные системные утилиты Linux: top, htop, btop |
| Анализ памяти и GC | jstat, jmap |
dotnet-gcdump |
Переменные рантайма GODEBUG=gctrace=1, встроенный эндпоинт pprof |
| Низкоуровневое профилирование | async-profiler |
dotnet-trace |
Аппаратный perf, встроенный pprof |
| Трассировка системных вызовов | strace, ltrace |
dotnet-trace |
Нативные утилиты ядра: strace, iostat, eBPF |
Ключевое преимущество философии Go заключается в глубокой гармонии со стандартными системными утилитами Linux. Инженеру DevOps или SRE не нужно настраивать тяжелые проприетарные агенты или открывать отдельные отладочные порты: достаточно подключиться к производственному серверу через SSH и за считанные секунды развернуть полную картину происходящего на уровне ядер, шин памяти и контроллеров дисков.
Итог
- Фундамент наблюдаемости: Инструменты
top,vmstat,iostatиperfобразуют незаменимый базис системной диагностики Go-приложений, отображая реальное поведение сервиса на стыке пространства ядра и аппаратного обеспечения. - Потоки M:N: Утилита
topпоказывает физические потоки операционной системы (M), а не прикладные горутины (G). Высокий системный процент времени%sy— верный маркер избыточных системных вызовов (write,madvise). - Метрики памяти: В утилите
vmstatпоявление любых ненулевых значений в колонке сброса подкачкиso > 0означает катастрофу для Go-сервиса: циклы сборщика мусора GC в сочетании со свопингом парализуют время отклика системы. - Дисковая подсистема: Утилита
iostatотражает эффективность страничного кэша ядра (Page Cache) и насыщение диска (%util,await). Высокая нагрузка устраняется буферизацией вbufioили интерфейсомio_uring. - Аппаратное профилирование: Системный инструмент
perfиспользует аппаратные счетчики PMU для поиска промахов кэша и узких мест в коде без искажений рантайма, идеально дополняя прикладной профайлерpprof.
Мы научились всесторонне оценивать состояние операционной системы и железа с помощью классических утилит Linux. Но что делать, если вам требуется проводить непрерывный, сверхглубокий и безопасный аудит системных вызовов, сетевых сокетов и скрытых утечек без малейших накладных расходов на переключение контекста и без перезапуска сервисов? В следующей лекции мы откроем революционную технологию современной инженерии: добро пожаловать в мир 61. eBPF. Современный способ наблюдать за ОС.