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-специфика

Bash
# Показать потоки (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 секунду:

Bash
vmstat 1 10
Text
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) детально визуализирует производительность дисковых контроллеров и виртуальных устройств.

Bash
# Расширенная статистика по дискам с шагом в 1 секунду (5 итераций)
iostat -x 1 5

Ключевые метрики

Text
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

  1. Конфигурация счетчиков: Ядро программирует регистры PMU процессора на генерацию прерывания при наступлении заданного числа аппаратных событий (например, каждые 100 000 выполненных инструкций или промахов кэша L3).
  2. Аппаратное прерывание: Процессор генерирует прерывание PMI (Performance Monitoring Interrupt), и ядро Linux перехватывает управление.
  3. Запись в кольцевой буфер: Ядро считывает текущий указатель команд (Instruction Pointer, RIP) и стек вызовов, записывая срез события в разделяемый кольцевой буфер (ring buffer).
  4. Анализ: Пользовательская утилита 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-приложения

Bash
# Запись профиля работы 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"]
  

Практические сценарии

  1. Паузы сборщика мусора вызывают всплески латентности (Latency Spikes):
    • Симптомы: В top наблюдаются резкие цикличные провалы %us, в vmstat кратковременно растет колонка b, внешние HTTP-клиенты получают таймаут.
    • Проверка: Запуск сервиса с флагом GODEBUG=gctrace=1 | grep gcwait или снятие профиля аллокаций pprof -alloc_objects.
    • Лечение: Временная корректировка GOGC=50, радикальное снижение аллокаций в горячих циклах через повторное использование буферов с помощью пула sync.Pool, замена динамических срезов []byte на mmap.
  2. Утечка горутин (Goroutine Leak):
    • Симптомы: В top показатель RSS (физическая память процесса) монотонно растет линейно вверх; со временем в vmstat начинает расти колонка подкачки si/so.
    • Проверка: Снятие дампов памяти pprof -inuse_space или профилирование общего объема аллокаций pprof -alloc_space, а также проверка эндпоинта /debug/pprof/goroutine?debug=1.
    • Лечение: Поиск горутин, заблокированных на незакрытых каналах, зависших контекстах context.Context или сетевых клиентах net/http без выставленных таймаутов Timeout.
  3. Яростная конкуренция за процессор на мьютексах (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 и за считанные секунды развернуть полную картину происходящего на уровне ядер, шин памяти и контроллеров дисков.

Итог

  1. Фундамент наблюдаемости: Инструменты top, vmstat, iostat и perf образуют незаменимый базис системной диагностики Go-приложений, отображая реальное поведение сервиса на стыке пространства ядра и аппаратного обеспечения.
  2. Потоки M:N: Утилита top показывает физические потоки операционной системы (M), а не прикладные горутины (G). Высокий системный процент времени %sy — верный маркер избыточных системных вызовов (write, madvise).
  3. Метрики памяти: В утилите vmstat появление любых ненулевых значений в колонке сброса подкачки so > 0 означает катастрофу для Go-сервиса: циклы сборщика мусора GC в сочетании со свопингом парализуют время отклика системы.
  4. Дисковая подсистема: Утилита iostat отражает эффективность страничного кэша ядра (Page Cache) и насыщение диска (%util, await). Высокая нагрузка устраняется буферизацией в bufio или интерфейсом io_uring.
  5. Аппаратное профилирование: Системный инструмент perf использует аппаратные счетчики PMU для поиска промахов кэша и узких мест в коде без искажений рантайма, идеально дополняя прикладной профайлер pprof.

Мы научились всесторонне оценивать состояние операционной системы и железа с помощью классических утилит Linux. Но что делать, если вам требуется проводить непрерывный, сверхглубокий и безопасный аудит системных вызовов, сетевых сокетов и скрытых утечек без малейших накладных расходов на переключение контекста и без перезапуска сервисов? В следующей лекции мы откроем революционную технологию современной инженерии: добро пожаловать в мир 61. eBPF. Современный способ наблюдать за ОС.